I’ve been neglecting my blog, which I blame on a combination of using twitter and being on a book project. More about those later. In the meantime, I’ve recently been working on some Grails projects and found an issue with unit testing controllers.
I’m now on Grails 1.3.5, and I’m trying hard to do unit tests rather than integration tests. I rapidly hit a couple of issues, which I want to log here mostly so I don’t forget how I resolved them.
Let’s look at a trivial Hello, World type of example. Say I have a Grails project called helloworld, with a single controller.
[sourcecode language=”java”]
package cc.hello
class WelcomeController {
def index = {
log.info "params: $params"
String name = params.name ?: ‘Grails’
render "Hello, $name!"
}
def redirectMethod = { redirect action:"other" }
def other = { render "Made it here" }
}
[/sourcecode]
This is (deliberately) very similar to the example in the Grails reference guide. If I specify a parameter called name, the index action returns “Hello, $name”, otherwise it returns “Hello, Grails!”. I also added a method to do a redirect, because the generated Grails controllers do that a lot and I want to be able to test them.
To keep the story short, here’s my unit test, which I’ll explain afterwards.
[sourcecode language=”java”]
package cc.hello
import grails.test.*
class WelcomeControllerTests extends ControllerUnitTestCase {
WelcomeController controller
protected void setUp() {
super.setUp()
controller = new WelcomeController()
mockController(WelcomeController)
}
void testIndexNoParameters() {
controller.index()
assertEquals "Hello, Grails!", controller.response.contentAsString
}
void testIndexWithName() {
controller.params.name = "Dolly"
//mockParams.name = "Dolly"
controller.index()
assertEquals "Hello, Dolly!", controller.response.contentAsString
}
void testRedirect() {
controller.redirectMethod()
assertEquals "other", controller.redirectArgs[‘action’]
}
}
[/sourcecode]
The key features of this test are:
- I have to call
super.setUp()
in mysetUp()
method, or callingmockController
throws aNullPointerException
. The actual error is “Cannot invoke containsKey() on a null object”. In other words, the maps for the mock objects aren’t being set up without calling setUp() in the superclass. This cost me a lot of searching to figure out. - Unlike mocking the domain objects, you can instantiate the controller and then call
MockController
afterwards. For domain classes you either have to mock the domain first, or call the version ofmockDomain
ormockForConstraintsTests
that takes a list of instances as its second argument. - The reference documentation tests the redirected method by comparing the generated URL to
controller.response.redirectedUrl
. That expression always returned null for me, however, and a search of the grails-users list showed it returns null for lots of people. Eventually I found in the excellent Grails in Action book by Peter Ledbrook and Glen Smith that theredirectArgs
map has the name of the redirected action under the"action"
key, so I can just compare to that. That’s what I’m doing above.
Hopefully these points will help keep someone else from making the same mistakes I did.
Leave a Reply