Grails—Unit Testing View Rendering (with custom TagLib namespace quirk)

Grails, programming, test

Background

GroovyPageUnitTestMixin

From the Grails guide, testing a view in a unit test involves:

  • Annotating the Specification with: @TestMixin(GroovyPageUnitTestMixin).
  • Calling render(view: '...', model: ...) or render(template: '...', model: ...) whose returned value will be a String of the HTML output.

Custom TagLibs

Normally, without doing anything else, if the view or template uses custom (i.e. not ones from the g: namespace) TagLibs, they will not be expanded. For example, if a view GSP was like this:

<div id="blah">
    <abc:decorate code="${obj}" format="a" />
</div>

the rendered content for the TagLib will be pretty much verbatim of the original GSP. The only exception is that any ${...} will be evaluated. For example:

<div id="blah">
    <abc:decorate code="Object@3234f21" format="a" />
</div>

And from the Grails guide, using mockTagLib(DecorateTagLib) (assuming the TagLib class for the abc:decorate tag is implemented in the TagLib class DecorateTagLib) will then trigger the TagLib:

@TestMixin(GroovyPageUnitTestMixin)
class MyTestSpec extends Specification {
    ...

    void testBlah() {
        given: 
        mockTagLib(DecorateTagLib)

        expect:
        ...
    }
}

will now render something like:

 
<div id="blah">
    {output of DecorateTagLib.decorate}
</div>

Note that the same effect can be accomplished using the @Mock() annotation on the class:

@TestMixin(GroovyPageUnitTestMixin)
@Mock([DecorateTagLib])
class MyTestSpec extends Specification {
    ...

    void testBlah() {
        given: 
            ...

        expect:
        ...
    }
}

And Now the Quirk…

If there were multiple TagLib classes sharing the same namespace (abc in this example) used in the GSP:
DecorateTagLib.groovy

class DecorateTagLib {
    static namespace = 'abc'

    def decorate = { attrs -> ... }
}

and FormatTagLib.groovy

class FormatTagLib {
    static namespace = 'abc'

    def format = { attrs -> ... }
}

and the GSP uses them both:

<div id="blah">
    <abc:decorate code="${obj}" format="a" />
    <abc:format val="${x}" />
</div>

and only some of them was mocked:

@TestMixin(GroovyPageUnitTestMixin)
@Mock([DecorateTagLib])
// NOTE there is no mocking of the FormatTagLib class implementing abc:format
class MyTestSpec extends Specification {
    ...

    void testBlah() {
        given: 
            ...

        expect:
        ...
    }
}

then running the test will bring up and error for the TagLibs that are not mocked. For example, the error will be like:
Tag [format] does not exist. No tag library found for namespace: abc

To fix this scenario, either:

  • Mock all the TagLibs used in the GSP that share the same namespace (e.g. abc in these examples), or
  • Redistribute the TagLibs using unique namespaces so that they do not share the same namespace. However, note that the rule stays the same for all the individual namespaces:
    • When at least one TagLib from a namespace is mocked, ALL other TagLibs used in the GSP with that namespace must also be mocked.