Tuesday, December 1, 2009

GroovyMag Plugin Corner: Testing

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 Publishers and Books 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...

Resources
Official Plugin Documentation: http://grails.org/Testing+Plugin

Mike Hugo’s presentation to the Groovy Users of Minnesota: http://www.piragua.com/2008/11/12/testing-plugin-presentation

Robert Fletcher’s very helpful blog post:
http://stateyourbizness.blogspot.com/2008/08/unit-testing-controllers-with-testing.html

2 comments:

Mike Miller said...

Thanks for the post. I got bit on my first attempt at unit testing when I tried to call some of the dynamic finder methods that were working in the integration tests.

This plugin should come in very handy.

Quick question: by doing this, don't the unit & intergration tests start to duplicate each other?

Dave Klein said...

Hi Mike,

Well, unit and integration tests are technical mechanisms provided by Grails and they are also conceptual ways of testing. The unit test mechanism has now been made more flexible so that you could write conceptual integration tests with it.

Whether or not you want to do that is debatable. However there are times when you want to test a single class and yet it relies on GORM which can be tricky to mock. Now it's easier.

As always, with great power comes great responsibility. :-)