Unit Testing Controllers in Grails 1.3.7
One benefit of Grails is its robust and versatile controller logic. It can render responses in a multitude of formats, validate input, and handle a lot of processing behind the scenes. This is all great and helpful during implementation, but when it comes to testing controllers, it is easy to get tripped up knowing exactly what to mock out and what to verify. In this post I’ll try to document some common and no-so-common issues I’ve run across unit testing controllers. I’ll focus primarily on HTML and JSON, although most of the logic can be applied to XML responses as well.
Setup
Let’s first start with the basics. In order to use some of the framework that Grails provides for testing, we will need to extend the ControllerUnitTestCase in our testing class. Among other things, for Grails to properly hook up the controller variable to our intended controller class, we need to follow two rules, as explained in the Grails documentation:
- Your test class needs to be in the same package as the controller class under test
- The name of your test class needs to be “<YourControllerName>Tests”
Doing these two steps will make testing our controller a lot easier. Finally, as with all Grails unit tests, be sure to include the setUp() and tearDown() methods (note the camel-case) to ensure everything works properly:
protected void setUp() {
super.setUp()
}
protected void tearDown() {
super.tearDown()
}
Input
In addition to the common mocks that you may encounter in regular service and domain object unit tests, controllers have a few extra properties that may be helpful to know. Most of these are specific to web applications, and deal with things such as the Spring MVC scope objects, requests, and URL parameters. Thankfully most of the hard work has been done for us since we extended the ControllerUnitTestCase class. All we have to do is understand the purpose of these methods and how to use them effectively.
Let’s start with a basic controller action and command object:
def foo = { SimpleCommandObject simpleCommandObject ->
if (params.id) {
render (template: 'fooTemplate', model: [ simpleCommandObject: simpleCommandObject ])
} else {
def response = [ note: 'No id' ]
render response as JSON
}
}
class SimpleCommandObject {
Long id
String code
static constraints = {
code (nullable: false, blank: false, size: 1..100)
}
}
Obviously this is a pretty non-sensical action, but it will serve for example’s sake. The first thing we notice is that there is a command object being used for validation. Even though the parameters are bound to the object when a web request is made, when testing we will need to manually set the properties on the object and pass it in to the action when we call the controller action. Simply setting the params will not work as the two are mutually exclusive. So we can set one up like so:
void testFooCommandObject() {
SimpleCommandObject simpleCommandObject = new SimpleCommandObject(id: 1, code: 'Testing')
controller.foo(simpleCommandObject)
}
If we want to ensure that the object meets constraints, we can call mockCommandObject(SimpleCommandObject.class) before creating our command object or mockForConstraintsTests(SimpleCommandObject, [simpleCommandObject]) afterwards to add the validate() suite of methods. The two are mostly equivalent, and most importantly call the addValidateMethod that we need. So now that we have successfully added a command object to our tests, the step is to mock the parameters. Luckily Grails makes this simple as well, and provides us with a map we can use. To add the id key that we need for the if statement in our action, we write:
mockParams.id = 1
Quick and painless. The same can be done with mockFlash, redirectArgs, forwardArgs, and mockSession, although mockSession is a Spring MockHttpSession, and not a map. Nonetheless, we can set up almost all the expectations for our controller using those variables.
Response
The crux of most unit tests is validating the output. Since Grails is so flexible with the output that controllers can render, this can be the trickiest part of testing controllers. I’ll give a brief rundown of some of the most common scenarios and how to test them. Looking back to our original example, we can see that if we have an id in our params, we render a template and pass the command object through the model. We can verify this behavior by extending our test:
void testFooWithId() {
mockCommandObject(SimpleCommandObject.class) // Option 1
SimpleCommandObject simpleCommandObject = new SimpleCommandObject(id: 1, code: 'Testing')
mockForConstraintsTests(SimpleCommandObject, [simpleCommandObject]) // Option 2. Only need one of the two
mockParams.id = 1// Remember that the command object will not auto-populate this, or vice versa
def response = controller.foo(simpleCommandObject)
assertEquals simpleCommandObject.id, response.simpleCommandObject.id
assertEquals simpleCommandObject.id, controller.renderArgs.simpleCommandObject.id
assertEquals 'fooTemplate', controller.renderArgs.template
}
A few things to note here. The most obvious is that there are numerous ways to verify the model from the controller. Whether passing the model in explicitly, or if we simply return a map from the action, the return value will have that object. Secondly, we can validate the template name through the renderArgs call. It’s important to note that renderArgs is just a map as well, so if we were rendering a view rather than a template, we would be able to access that property through renderArgs.view. The same holds true for redirectArgs and forwardArgs. These are useful is we are rendering a gsp, but what if we want to return plain text, or JSON? For that we need to make a few changes to our tests.
First let’s remove the mockParams.id declaration so that we fall to the else part of our action. This will mean the controller will return a JSON response using the render syntax. We can assert that the proper JSON is rendered like so:
void testFooNoId() {
SimpleCommandObject simpleCommandObject = new SimpleCommandObject(id: 1, code: 'Testing')
mockForConstraintsTests(SimpleCommandObject, [simpleCommandObject])
controller.foo(simpleCommandObject)
def jsonResponse = JSON.parse(controller.response.contentAsString)
assertEquals 'No id', jsonResponse.note
}
When we verify the response, we need to parse it with the grails.converters.JSON class, which then allows us to much more easily verify its contents. Now, it’s worth noting that this is just one way to render json, and that Grails 2.0 has better support for building JSON. Until then, Grails still offers a pretty lightweight and straightforward way to render JSON.
One last gotcha
Let’s say we were to render our response using an explicit contentType:
def response = [error: 'No id']
render(contentType: "text/json") {
response
}
Examining the controller.renderArgs, we would see that it only returns contentType:text/json, but not the map that we wanted to render. We’ve run into an issue. This article does a good job explaining the problem and a workaround, but as far as I’m aware, the only fix is change the controller metaclass’s render method or to use a different syntax for rendering. Fortunately this problem does not seem to exist for rendering xml.
Last Point
Hopefully this post has shed some light has shed some light on a few of the less-documented gotchas you might come across, and has demonstrated that with a little knowledge, testing Grails controllers is really quite simple. Grails’ wide array of rendering options may seem complicated at first, but fortunately it has provided us with just as extensive of a testing framework. If you have any questions or comments (or if you’re still not writing tests for your controllers!), please feel free to post here and I’ll do my best to address them.
Igor Shults