act:ualise | technology

agile software development, software quality, scaling, testing and other tech

JSON Webtests with Grails

| 5 Comments

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

    }

5 Comments

  1. Thank you! I’ll try and see how that works…

  2. 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

  3. 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.

  4. 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.

  5. 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!

Leave a Reply

Required fields are marked *.

*