I recently figured out how to use WebTest for functional testing of Grails controller actions that render JSON. That said, I’m not convinced it’s the best way – I’m fairly sure the gFunc plugin would do it nicely, though I ran into problems with it clean compiling the whole app on every run.
Custom steps
It’s been possible to add custom steps to Webtests for some time. Assuming you have v0.6 of the plugin (or you use Grails 1.1), then this writeup provides some useful background and also a ‘Hello, World’ type example.
In an ideal world
On the surface it seems that we could therefore have a jsonVerify step which is quite simply:
class JsonVerifyStep extends Step {
String expected
void doExecute() {
def jsonServed = context.currentResponse.inputStream as JSON
def jsonExpected = expected as JSON
assert jsonExpected, jsonServed
}
}
Annoyingly, it’s not this easy.
Grails, Webtest and (sigh) Classpath’s
Webtest is spawned by a forked Ant process (see ${pluginDir}/webtest-n.n/scripts/call-webtest.xml) which means you get a limited classpath due to JAR version conflicts.
So it’s not possible to add the Grails classpath (or even just $GRAILS_HOME/dist/grails-web-n.n.jar) which contains all the handy JSON library code that we’re so accustomed to when rendering JSON responses.
Solution (has some camembert)
My “solution” was to add two jars (json-lib & ezmorph) to your Grails lib and also tweak the call-webtest.xml file with the following change:
<fileset dir="${grailsHome}/lib" includes="commons-cli*.jar,commons-beanutils*.jar"/>
With the resulting custom step you can test your JSON response
import com.canoo.webtest.steps.Step
import net.sf.json.JSON
import net.sf.json.groovy.GJson
import org.apache.commons.io.IOUtils
import org.apache.log4j.Logger
import net.sf.json.test.JSONAssert
class JsonVerifyStep extends Step {
private static Logger log = Logger.getLogger(JsonVerifyStep)
String expected
void doExecute() {
GJson.enhanceClasses() // neccessary for the net.sf.JSON stuff in Groovy
def jsonServed = IOUtils.toString(context.currentResponse.inputStream) as JSON // wants a string
def jsonExpected = expected as JSON
JSONAssert.assertEquals jsonExpected, jsonServed
}
}
And you would implement your webtest as follows:
def testSomeJSONResponse() {
webtest('Example JSON webtest') {
invoke('/controller/actionJSON')
jsonVerify(expected: '{"totalRecords":2,"results":[{"id":16,"year":2009,"name":"ZZ Top"},{"id":2,"year":2009,"name":"Aerosmith"}')
}
}
July 24, 2009 at 6:37 am
Thank you! I’ll try and see how that works…
July 24, 2009 at 11:43 am
Our team uses REST services exclusively using jersey (wish it was grails) with extjs on the frontend.
I have written about how we write tests for our REST Services here using groovy and HttpBuilder.
http://jlorenzen.blogspot.com/2009/01/testing-rest-services-with-groovy.html
July 24, 2009 at 12:15 pm
James,
Thanks for your comment; I had in fact come across your posting and I liked it’s brevity/simplicity.
Using Webtest allows for the automation by Grails of bringing up the application and then running our functional tests against it, whereas Units by themselves don’t do that.
That said, this experiment came as a consequence of a problem I ran into writing a controller action test. I’ll try to document the problem I had if I can’t solve it on a revisit.
July 27, 2009 at 1:21 am
Jerome,
I tried this and it seemed to work pretty nicely with gfunct
def array = new JSONArray(“[${response.contentAsString}]“)
notice how I’ve had to add [ and ] at the start and end of the string. Not sure why it’s needed but hey at least you then can work with the json array as you need it.
August 19, 2009 at 8:44 am
Yeah, that works fine in the functional-test plugin. The trick is when you want to PUT or POST JSON data back to the server!