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