Sunday, June 13, 2010

Mocking Taglibs with Grails Units

Grails taglibs are a million times better than JSP taglibs (requiring way less busywork), but they still can be tricky to unit test. Here are some tricks I've learned so far:

Basic Unit Testing

The first trick is that there's basically no official documentation for how to write even basic taglib unit tests. There is, however, a nice TagLibUnitTestCase class provided with grails that's easy to use (once you figure it out). For a simple taglib like this:

package myapp class MyTagLib { // optionally use <my:tag> namespace instead of <g:tag> static namespace = 'my' def heading = { attrs, body -> def level = attrs.level ?: 1 out << "<h$level>" << body() << "</h$level>" } }

You can write a TagLibUnitTestCase like this:

package myapp import grails.test.TagLibUnitTestCase class MyTagLibUnitTests extends TagLibUnitTestCase { void testHeadingWithNoLevelAndNoContent() { tagLib.heading [:], {''} assertEquals '<h1></h1>', tagLib.out.toString() } void testHeadingWithLevelAndSimpleContent() { tagLib.heading level: '2', {'simple'} assertEquals '<h2>simple</h2>', tagLib.out.toString() } }

The TagLibUnitTestCase class automatically determines which taglib you're testing based on the name of your test class, and sets up a stub taglib as the tagLib member of the test class. Content written to the out variable in your taglib is written to the out (StringWriter) member of this stubbed taglib.

Using Codecs

If you use one of grails' encodeAs string methods (like encodeAsHTML()) in your taglib, you need to explicitly set up that codec in your unit test, using the loadCodec helper method, like this:

package myapp import grails.test.TagLibUnitTestCase import org.codehaus.groovy.grails.plugins.codecs.HTMLCodec class MyTagLibUnitTests extends TagLibUnitTestCase { protected void setUp() { super.setUp() loadCodec(HTMLCodec) } }

Another thing to note specifically about the HTMLCodec is that encodeAsHTML() escapes double-quotes, but not single-quotes — so when you output html from a taglib, make sure you always use double-quotes for attributes in your html. The reason for this is that if you print out a variable in a single-quoted attribute — or you change your code later to print out a variable — and that variable's value came from some content that your users can manipulate, you're vulnerable to xss — even though you used encodeAsHTML() to escape that variable's content. Here's a quick example of what not to do:

class MyTagLib { def foo = { attrs, body -> def bar = "' onmouseover='alert(\"xss\")'" out << "<a href='" << bar.encodeAsHTML() << "'>Bar</a>" } }

When the taglib is used, a single-quote in the variable content "breaks-out" of the attribute, giving the attacker full access to act as the user (presumably to do something nastier than simply display an alert):

<a href='' onmouseover='alert("xss")'>Bar</a>

Calling Taglibs from Taglibs

The first trick for calling taglibs from other taglibs is to make sure always to return an empty string from all your taglibs:

package myapp class MyTagLib { // optionally use <my:tag> namespace instead of <g:tag> static namespace = 'my' def linkToWikipedia = { attrs, body -> out << "<a href=\"http://en.wikipedia.org/wiki/${body().encodeAsHTML()}\">${body().encodeAsHTML()}</a>" '' } }

Otherwise you get duplicated text or other weird values in the stubbed output-stream when you try to unit-test the taglib. When you actually call one taglib from another taglib, the second trick is to append the result from the called taglib to the output of the caller:

package myapp class MyTagLib { // optionally use <my:tag> namespace instead of <g:tag> static namespace = 'my' def heading = { attrs, body -> def level = attrs.level ?: 1 out << "<h$level>" << (attrs.wikipedia ? linkToWikipedia([:], body) : body()) << "</h$level>" '' } def linkToWikipedia = { attrs, body -> out << "<a href=\"http://en.wikipedia.org/wiki/${body().encodeAsHTML()}\">${body().encodeAsHTML()}</a>" '' } }

Otherwise the output of the called taglib is discarded when you run your app in the full grails environment.

And the third trick is that when you're trying to simulate nested taglibs in a unit test, just call the nested taglibs in the container's closure, and don't do anything else — don't try to append their output to the stubbed output-stream or anything fancy like that; for example, when you're trying to simulate this GSP code:

<my:table in="mydata" var="data"> <my:tr><my:td>${data.id}</my:td><my:td>${data.name}</my:td></my:tr> </my:table>

Your unit test should look like this:

package myapp import grails.test.TagLibUnitTestCase class MyTagLibUnitTests extends TagLibUnitTestCase { void testTableWithDataAndTwoCells() { def mydata = [[id:'123', name:'Foo'], [id:'456', name:'Bar']] tagLib.table([ 'in': mydata, 'var': 'data' ], { m -> tagLib.tr([:], { tagLib.td([:], { "${m.data.id}" }) tagLib.td([:], { "${m.data.name}" }) '' }); '' }) assertEquals '<div class="data"><table><tbody><tr class="odd">' + '<td>123</td>' + '<td>Foo</td>' + '</tr><tr class="even">' + '<td>456</td>' + '<td>Bar</td>' + '</tr></tbody></table></div>', tagLib.out.toString() } }

Note that in the example test, the closures simulating the content of the my:table and my:tr taglibs both return an empty string (so you don't get extra junk in the output, as mentioned above); and although when you use (this made-up and hypothetical) my:table in GSP code you can reference the data variable that my:table sets directly (like ${data.name}), in the test you have to reference it as it is actually passed — as an entry in the map passed to the tag's body closure (like ${m.data.name}, where m is the first argument of the table body closure in the example test code).

Beyond Grails Stubs

There are some things that aren't covered by the stub tagLib object provided by the TagLibUnitTestCase. For example, you can't stub the request params map, and accessing the taglib's controllerName or actionName properties fails with a giant flaming fireball.

The simplest way to deal with that is with some groovy metaprogramming. For example, say you want to test a taglib that grabs some data from the request params map:

package myapp class MyTagLib { static namespace = 'my' def seeAlsoWikipedia = { attrs, body -> if (params.q) out << "See also <a href=\"http://en.wikipedia.org/wiki/${params.q.encodeAsHTML()}\">${params.q.encodeAsHTML()}</a> in wikipedia" } }

You can use the tagLib instance's metaClass property to stub out the params map:

package myapp import grails.test.TagLibUnitTestCase class MyTagLibUnitTests extends TagLibUnitTestCase { void testSeeAlsoWikipediaWithAQuery() { tagLib.metaClass.params = [q: 'foo'] tagLib.seeAlsoWikipedia [:], {''} assertEquals 'See also <a href="http://en.wikipedia.org/wiki/foo">foo</a> in wikipedia', tagLib.out.toString() } }

GMock and Expectations

There's a good deal of overlap between the GrailsUnitTestCase class (superclass of TagLibUnitTestCase) and the functionality provided by GMock (in the form of the GMockTestCase class), but GMock still can be useful when you need to stub functionality not already stubbed by TagLibUnitTestCase — and you'd like to do it just by setting up some simple expectations (ie "mocks" in TDD-correct terminology).

GMock has some pretty good documentation about how to include the GMock jar into Grails; to integrate GMock into a TagLibUnitTestCase, use the @WithGMock annotation:

package myapp import grails.test.TagLibUnitTestCase import org.gmock.WithGMock @WithGMock class MyTagLibUnitTests extends TagLibUnitTestCase { void testSeeAlsoWikipediaWithAQuery() { mock(tagLib) { params.returns([q: 'foo']) } play { tagLib.seeAlsoWikipedia [:], {''} assertEquals 'See also <a href="http://en.wikipedia.org/wiki/foo">foo</a> in wikipedia', tagLib.out.toString() } } }

Use GMock's mock() method to mock an object instance (in the above example, the tagLib object already stubbed initially by TagLibUnitTestCase); in the closure passed to the mock() method, set up your expectations. In the above example, the one and only expectation for the tagLib object is for its params property to be accessed; when accessed, GMock will make it return [q: 'foo']. After you've set up the mocks and expectations, everything in the play {} closure will be executed with those mocks.

The example above performs the same exact test as the example before — the only functional difference is that GMock also validates your expectation that params will be accessed.

GMock with Other Taglibs

One thing GMock is especially good for is when a taglib you want to test calls some other taglib. When unit testing, you don't really want to test that other taglib; GMock can help by replacing that other taglib with a mock which validates your expectation that the other taglib was called with the right attributes — but without doing anything other than returning some fixed content.

For example, say you've got a taglib that prints the label, content, and errors for a given property, rendering the errors with grails' standard g:hasErrors and g:renderErrors tags:

package myapp class MyTagLib { static namespace = 'my' def propertyLine = { attrs, body -> def beanAndField = attrs.findAll { ['bean','field'].contains(it) } out << '<div class="property">' out << "<label>${attrs.label}</label>" out << '<div class="content">' << body() << '</div>' out << g.hasErrors(beanAndField, { out << '<div class="errors">' << g.renderErrors(beanAndField, {''}) << '</div>' '' }) out << '</div>' '' } }

You can pretty much assume that g:hasErrors and g:renderErrors has already been tested thoroughly and is working — you just need to test that you're correctly passing the bean and field parameters to them. With the help of GMock, you can test it like this:

package myapp import grails.test.TagLibUnitTestCase import org.gmock.WithGMock @WithGMock class MyTagLibUnitTests extends TagLibUnitTestCase { void testPropertyLineWithBeanAndField() { def bean = new Expando() mock(tagLib.g) { hasErrors([bean:bean, field:'foo'], match{ it instanceof Closure }).returns('_errors_') } play { tagLib.errors [bean:bean, field:'foo'] {''} assertEquals '<div class="property">' + '<label>null</label>' + '<div class="content"></div>' + '_errors_' + '</div>', tagLib.out.toString() } } }

The GMock expectations in the above example validates that g:hasErrors is called with the right bean and field properties, and is passed a closure. It also makes g:hasErrors return '_errors_', so you can validate that the content g:hasErrors would normally generate is in the right place in the context of all the other output generated by your taglib.

Built-in Groovy Mocking

Groovy also has built-in stubbing and mocking functionality, via its StubFor and MockFor classes. These are most useful when you want to stub an object that your taglib creates itself, like the TokenGenerator class in this example:

package myapp class MyTagLib { static namespace = 'my' def onetimeToken = { attrs, body -> def onetime = new TokenGenerator() out << '<input type="hidden" value="' << onetime.generateToken() << '">' '' } }

You can mock the TokenGenerator class like this:

package myapp import grails.test.TagLibUnitTestCase class MyTagLibUnitTests extends TagLibUnitTestCase { void testOnetimeToken() { def mockTokenGenerator = new MockFor(TokenGenerator) mockTokenGenerator.demand.generateToken { '123' } mockTokenGenerator.use { tagLib.onetimeToken [:] {''} assertEquals '<input type="hidden" value="123">', tagLib.out.toString() } } }

The code inside the use closure will use your mocked TokenGenerator behavior in place of the regular TokenGenerator behavior (ie it will return '123' for the generateToken() method), and it will raise an exception if generateToken() wasn't called within the use closure.

No comments:

Post a Comment