Absolutely No Machete Juggling

Rod Hilton's rants about stuff he cares about way too much...

Testing Against Template Renders in Grails

I work with Grails a lot and while I really enjoy it for the most part, there are definitely some weird quirks of the framework.

One such quirk is something I encounter whenever I want to write unit tests against grails controller methods that render out templates directly. This isn't something I do very often - generally I prefer rendering out JSON and parsing it with client-JS - but in some cases when there's a lot of markup for a page element that you want to be updateable via ajax, it makes sense to render out a template like render(template: 'somePartial') directly from a controller method.

Unfortunately, these kinds of methods are very difficult to write tests against. While a normal render exposes a model and view variable that you can test against, for some reason using a render with a template doesn't seem to do this.

I've seen lots of solutions where you stuff a fake string matching the name of the template using some metaclass wizardry, but then you're stuck dealing with some semi-view stuff in what you might want to simply be a unit test about the model values placed by the controller method.

However, based on this StackOverflow post, I've written a quick-and-dirty little monkeypatch that exposes the model and view variables in your test, and populated with the values relevant to the template.

I've got this method in a TestUtil class:

static def ensureModelForTemplates(con) {
    def originalMethod = con.metaClass.getMetaMethod('render', [Map] as Class[])
    con.metaClass.render = { Map args ->
        if (args["template"]) {
            con.modelAndView = new ModelAndView(
                args["template"] as String, 
                args["model"] as Map
            )
        }
        originalMethod.invoke(delegate, args)
    }
}

Then I can call this method with the controller as a parameter anywhere before I invoke the controller action. It can go in a setup or @Before method, it seems to work from both Spock tests and the builtin Grails testing framework.

So if we have this example:

class ExampleController {

    def index() {
        def bigName = params.name.toUpperCase()

        render(template: "partial", model: [
            one: "hello",
            two: bigName
        ])
    }
}

This test will do what we want:

@TestFor(ExampleController)
class ExampleControllerSpec extends Specification {

    def setup() {
        TestUtil.ensureModelForTemplates(controller)
    }

    def shouldHaveModelAndViewExposed() {
        given:
        params.name = "Rod"

        when:
        controller.index()

        then:
        view == "partial"
        model.one == "hello"
        model.two == "ROD"
    }
}
comments powered by Disqus