Unit testing Grails controllers, revisited

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 my setUp() method, or calling mockController throws a NullPointerException. 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 of mockDomain or mockForConstraintsTests 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 the redirectArgs 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

This site uses Akismet to reduce spam. Learn how your comment data is processed.