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"}')
}
}
