In the December 2008 issue of GroovyMag, I covered the Grails Testing plugin. This plugin provided the new Grails 1.1 testing features to 1.0.x applications. With that in mind, this may seem a bit out of date. But I happen to know that there are still some applications being developed in Grails 1.0.4, and the classes and methods described here, though built-into Grails 1.1 and above, are still applicable. So, just to be clear: If you are using Grails 1.1 or above, you can still use these classes / methods, but you don't have to install the plugin.When writing unit tests, the goal is to test a single class in isolation, with any collaborating objects being replaced by mocks or stubs. This will lead to less fragile code that can work with different implementations of collaborating objects, and it will help to isolate bugs when they appear. Unit tests are also much faster to run, since they don’t require resource-intensive services and containers to be loaded. However, with all of the functionality that Grails magically adds to your domain classes, controllers, services, and taglibs, it can be quite difficult to write unit tests for these artifacts. It can be done, and I know several determined developers who have, but many others just take the easy (and slow) road and write only integration tests. Some take the dead-end road and write no tests at all. Integration tests test a class with its collaborators, which means that the Grails goodness is all there for you. These tests are important to have, but they should not take the place of unit tests. Another concern with integration tests as your only automated tests is that they take much longer to run, which means that they will likely be run less often.
Well, now there is no excuse not to write unit tests for your Grails applications. In this Plugin Corner we will see how the Testing plugin, by Peter Ledbrook, makes it plain easy to write unit tests for Grails artifacts (domain classes, controllers, services and taglibs). This plugin does for unit testing what GORM does for persistence. It provides mock implementations of almost all of the dynamic methods added to your artifacts by Grails. The plugin includes four new
TestCase
classes that all descend from
GroovyTestCase
, which itself extends from JUnit’s
TestCase
.
GrailsUnitTestCase
introduces most of the powerful meta-programming magic in this plugin, and it will be sufficient for testing domain classes and services that use domain classes. For testing controllers and TagLibs you can probably guess which classes to use.
Let’s take a look at just how easy-to-use and powerful this plugin is. First we’ll install it:
grails install-plugin testing
Now to take advantage of the goodness this plugin gives us, we just need to have our unit tests extend one of the new
TestCase
classes. In the following example we’ll extend the
ControllerUnitTestCase
class.
class BookControllerTests extends grails.test.ControllerUnitTestCase{
def b1, b2
void setUp(){
super.setUp()
controller = new BookController()
b1 = new Book(title:’Programming Groovy’, author:’Venkat Subramaniam’)
b2 = new Book(title:’Grails in Action’, author:’Glen Smith’)
mockDomain(Book, [b1, b2])
}
ControllerUnitTestCase
has a protected
controller
property. In our
setup()
method we set this property to a new instance of our
BookController
. Then we create a couple
Book
instances and call the uber cool
mockDomain()
method. This method takes a class (
Book
) and a list (
[b1, b2]
). Once this method has been called, we can call methods like
list()
,
get()
,
save()
,
delete()
, and even
findAllByXXX()
. The list becomes an in-memory database. In our example we passed two existing instances in the list. When we called
mockDomain()
, the object instances in the list were “saved” to the list. Alternatively, we could have passed in an empty list and added the instances by calling
save()
. In either case, the objects are assigned
id
values based on the order they are added. This is important, because most of the actions in a standard Grails controller use the
id
property to find objects.
void testShow(){
controller.params.id = 1
def model = controller.show()
assertEquals(b1, model.bookInstance)
}
When we call our controller’s
show
action it will call
Book.get()
. This will be performed by the mock implementation of
get()
that was added to
Book
when we called
mockDomain()
, which will retrieve the instance from the list. I point this out because the Testing plugin makes working with domain classes so seamless that it sometimes feels like we’re still writing integration tests.
void testBookNotFound(){
controller.params.id = 3
controller.show()
assertEquals(“Book not found with id 3”, mockFlash.message)
}
In our second test we set
params.id
to a number we know does not exist, to ensure that the proper error message is stored in the flash scope. Notice that what we check is not
flash
but
mockFlash
.
mockFlash
is a
Map
that is declared in the
MVCUnitTestCase
, the super class of the
ControllerUnitTestCase
.
MVCUnitTestCase
also introduces
mockRequest
,
mockResponse
,
mockSession
, and more.
A unit test for the scaffolded actions is of questionable value, so let’s try a unit test for a custom service. Our service method creates a batch of
Publisher
s and
Book
s from XML data. Here’s the test:
class BatchServiceTests extends GrailsUnitTestCase {
def batchService
def bookInstances = []
def publisherInstances = []
void setUp(){
super.setUp()
mockDomain(Book, bookInstances)
mockDomain(Publisher, publisherInstances)
batchService = new BatchService()
}
This test class extends
GrailsUnitTestCase
because we need the domain class mocking, but we do not need the features introduced in
MVCUnitTestCase
or later. We are defining the lists that will be passed to
mockDomain()
ahead of time, since we are not creating any domain instances in our test. Notice also that we are mocking both the
Publisher
and the
Book
domains. This will enable us to mock the relationship logic as well. Finally we create an instance of the service that we are going to test.
void testLoadBooks() {
def data = “””
<publishers>
<publisher name=”Apress”>
<book title=”Definitive Guide to Grails” author=”Graeme Rocher”/>
<book title=”Beginning Groovy and Grails” author=”Christopher Judd”/>
</publisher>
<publisher name=”Manning”>
<book title=”Groovy in Action” author=”Dierk Koenig”/>
</publisher>
</publishers>
“””
batchService.loadBooks(data)
def p = Publisher.findByName(‘Apress’)
assertEquals(2, p.books.size())
assertEquals(“Groovy in Action”, Book.findByAuthor(“Dierk Koenig”).title)
}
After calling our service’s
loadBooks()
we are able to call
Publisher.findByName()
to verify that the two books by Apress are there. We also verify that Dierk’s classic work is there by calling
Book.findByAuthor()
. These finder methods are not available in unit tests, but the Testing plugin is providing mock implementations of them using object instances held in lists.
Here’s a snippet of our
BatchService.loadBooks()
method:
def loadBooks(String data) {
def publishers = new XmlSlurper().parseText(data)
publishers.publisher.each {pub ->
def p = new Publisher(name:pub.@name.text())
pub.book.each{book ->
def b = new Book(title:book.@title.text(), author:book.@author.text())
p.addToBooks(b)
}
p.save()
Notice how we are calling
Publisher.addToBooks()
to place the
Book
instances in the
Publisher
’s
books
collection and finally calling
p.save()
. These methods are also being mocked by the Testing plugin. Again it’s easy to forget that we’re writing unit tests and not integration tests, that is until we see how fast they run!
The documentation on the Testing plugin is pretty good, but still leaves some holes. If you want to take full advantage of the plugin I recommend reading through the source code that is included in your project once you install the plugin. It’s mostly Groovy code so, of course, it reads like a good novel. There are also some other resources on the web to help you get going.
With this plugin there is really no need to fear unit testing your Grails artifacts. So, if you’ve been writing integration tests instead of unit tests or if you are not writing any tests
at all, then run (don’t walk) to your nearest Grails project and install this plugin. You’ll be glad you did and so will your team mates, your manager, your customers, your spouse, your neighbors...
ResourcesOfficial Plugin Documentation:
http://grails.org/Testing+PluginMike Hugo’s presentation to the Groovy Users of Minnesota:
http://www.piragua.com/2008/11/12/testing-plugin-presentationRobert Fletcher’s very helpful blog post:
http://stateyourbizness.blogspot.com/2008/08/unit-testing-controllers-with-testing.html