Grails JUnit tests are, as you are no doubt aware, divided into unit tests and integration tests. Unit tests run fast, aren't dependent on the database or Spring context but mean you have to live without all the dynamic magic that Grails attaches to your artefacts (controllers, taglibs, domain classes, etc.) This can certainly lead to some sub-optimal test code.
Lets consider an example. I want to write some test coverage for a controller that looks up a domain object based on a parameter and places it in the model.
Usual disclaimer: of course, in the real world I'd have written the test first, etc. Nothing much to the controller:
class LolrusController {
def show = {
def myLolrus = Lolrus.findByName(params.lolrusName)
render(view: 'lolrus', model: [lolrus: myLolrus])
}
}
and here's the domain class:
class Lolrus {
String name
String mood
static hasMany = [posessions: Item]
static constraints = {
name(unique: true)
posessions(validator: { posessions ->
return posessions.any { it.type == 'bukkit' }
})
}
String toString() { "Lolrus[$name]" }
boolean equals(Object o) { return o instanceof Lolrus && o.name == name }
int hashCode() { return 37 * name.hashCode() }
}
Okay, so a reasonable test (ignoring edge cases for now) could be to create a couple of domain objects, make a request to the controller and verify that the correct domain object is retrieved. Simple, right?:
class LolrusControllerTests extends GroovyTestCase {
def controller
def lolrus1, lolrus2
void setUp() {
controller = new LolrusController()
lolrus1 = new Lolrus(name: 'Hugh')
lolrus2 = new Lolrus(name: 'Alan')
[lolrus1, lolrus2]*.save(flush: true)
}
void testShowActionFindsCorrectLolrusAndSticksItInModel() {
controller.params.lolrusName = lolrus1.name
def model = controller.show()
assertEquals('/lolrus/show', controller.modelAndView.viewName)
assertEquals(lolrus1, controller.modelAndView.model.lolrus)
}
}
Unfortunately, not so simple. The test fails and reports
expected:<Lolrus[Hugh]> but was:<null>
What we've forgotten of course is that
Lolrus has a mandatory field
mood and a custom validation on its
posessions association that requires it to have a bukkit. The two
Lolrus instances we tried to create in
setUp couldn't be saved. Great. Well, let's just add that to the test set up:
void setUp() {
controller = new LolrusController()
lolrus1 = new Lolrus(name: 'Hugh', mood: 'worryingly cheerful')
lolrus1.addToPosessions new Item(type: 'bukkit')
lolrus2 = new Lolrus(name: 'Alan', mood: 'mildly distressed')
lolrus2.addToPosessions new Item(type: 'bukkit')
[lolrus1, lolrus2].each {
assert it.save(flush: true), it.errors
}
}
Okay, it works but look how much of what's going on in
setUp is actually nothing to do with anything the test cares about. This is a very simple example with a single test case. Imagine how much worse that
setUp method is going to get when we add more tests or the
Lolrus domain object gets more complex... Imagine the refactoring required when the
Lolrus domain object acquires other constraints... Imagine the query is more complex requiring more domain objects to be set up to really prove it works...
Okay, we could mock the
Lolrus class, but the code for that isn't much cleaner and alarm bells start ringing when you start using mocks in an integration test. Shouldn't this be a unit test? Sure, that would be nice but we're using the
render dynamic method in our controller, we'll have to mock that out on the metaClass, mock out the findByName method on
Lolrus, etc. etc. Too much hassle right? Not any more.
grails install-plugin testing
Allows us to (among other things) write controller unit test cases like this:
class LolrusControllerUnitTests extends grails.test.ControllerUnitTestCase {
def controller
def lolrus1, lolrus2
void setUp() {
super.setUp()
controller = new LolrusController()
lolrus1 = new Lolrus(name: 'Hugh')
lolrus2 = new Lolrus(name: 'Alan')
mockDomain(Lolrus, [lolrus1, lolrus2])
}
void testShowActionFindsCorrectLolrusAndSticksItInModel() {
controller.params.lolrusName = lolrus1.name
controller.show()
assertEquals('show', renderArgs.view)
assertEquals(lolrus1, renderArgs.model.lolrus)
}
}
Much neater. We're not worrying about the constraints required to set up valid
Lolrus instances, but neither are we having to mock out the
findByName method.
The
mockDomain method allows you to set up some domain objects for which all the usual Grails domain class dynamic methods will work - including
findBy...,
addTo..., etc. The collection passed as the second argument to
mockDomain defines all the instances that exist and they can be retrieved just as a real domain object could. The
mockDomain method co-exists happily with regular Groovy mocks and the testing plugin also provides some lighter mocking capabilities via its own
mockFor method.
Additionally the controller's
render method and
params property work without any work on our part. This is true of all the dynamic properties and methods of the controller,
request, response, params, controllerName, redirect, etc.
Not only that, but this test runs faster than the integration test as it's not configuring the Spring container or an in-memory database. That speed difference only grows as the project becomes more complex - it's a couple of seconds between the two tests here, but in a more complex project it's a lot more.
There are a couple of gotchas (the plugin isn't fully mature). First off you need to remember to call
super.setUp() in order that all the Grails stuff happens. The
ControllerUnitTestCase class will figure out based on the class naming convention which controller class it needs to mock up. Secondly, the domain class
get method doesn't seem to work with non-standard identifier types at the moment (
i.e. anything other than a
Long). However, this unit testing support is intended to get rolled into Grails 1.1 so it should improve rapidly.
This is only part of what the testing plugin can do. Support for unit testing domain class constraints, etc. is also included. I've also been working on a
TagLibUnitTestCase that works along the same lines as
ControllerUnitTestCase.