Upgrading to Grails 2 Unit Testing

| Comments

Grails 2 has a lot of great new unit testing features that make many test scenarios easier.

The grails documentation does an OK job of describing some of the new features, but there really wasn’t anywhere that I could find that had a comprehensive list of changes you should make to your code when migrating from grails 1.3.X to 2.0.X.

This blog post is the list of changes that I wished I had when I started to migrate our code.

I found these pages helpful in figuring things out:

The company I’m consulting at made a large investment in unit testing in the grails 1.2/1.3 days with 500-600 unit tests that heavily leverage custom extended base classes, Mixins, and the grails 1.3 way of doing unit testing. This investment helped us keep most of our testing quick in unit tests, but has made this the most painful grails upgrade cycle I’ve been through (more painful than the grails 1.0->1.1 upgrade, which was the previous worst).

There have been surprisingly few changes to the actual production code to get things going, the vast majority of changes have been in how unit testing works.

General Test Upgrade Tips

  • If you use an IDE with a debugger, it’s very useful to have a separate copy of your code that works against grails 1.3.7 so that you can have 2 instances of IntelliJ and can step through line by line in each codebase to see where things diverge.
  • Methods with @Test, @Before, and @After MUST be declared public void or else they won’t be picked up
  • I had real problems with using the new grails annotations (like @Mock) while extending a custom super-class, I haven’t dug to the bottom of this yet, but the grails annotations seem to have trouble with class hierarchies, avoid them if possible
  • To that end, if you use the new grails annotations (like @TestFor), you MUST remove the old extends GrailsUnitTestCase
  • Integration tests should also never extend GrailsUnitTestCase, they should instead extend GroovyTestCase and just use assert
  • I’ve hit a number of test pollution issues when refactoring my code, where one test hasn’t properly cleaned up after itself affecting other tests downstream. If your test passes in isolation, you’ll need to run the tests in the same order as grails test-app does and slowly take out tests till your tests passes. Then you know which test is causing the pollution. The grails 2 test runner doesn’t show you the test order, but you can get it by setting a breakpoint in the JUnit4GrailsTestType.doPrepare method (line 54 as of grails 2.0.3) and executing this in your IDE: testClasses.name.each { println it }. Then you can take that list, join it together into one line and prepend grails test-app on it to run them in order.

Another way to get the in-order list of test classes from the test reports after a failed test using a little grep and sed:

grep testsuite target/test-reports/TESTS-TestSuites.xml | grep -v testsuites | cut -d\  -f8-9 | sed -E 's/name="(.*)" package="(.*)"/\2.\1/' | grep .

Mocking Class Methods by mucking with the metaClass

Groovy is now more stringent on closure arguments matching the type signature of a method it’s meant to mock. If the method you’re mocking types it’s variables, you need to match that with your closure.

grails 1.3.x:

MyDomain.metaClass.'static'.executeUpdate = { theQuery, theParams -> return}

Grails 2.0.x:

MyDomain.metaClass.'static'.executeUpdate = { String theQuery, Collection theParams -> return}

Otherwise it won’t find your closure and will still call the regular method you’re trying to mock. Put a breakpoint/println in your mocked methods to ensure that they’re being called by the code under test.

Unit Testing Domain Classes in Grails 2


grails 1.3.x:

class MyTests { 

Grails 2.0.x:

@Mock([Person, Vehicle])
class MyTests {

If you’ve given mockDomain instances to save, you’ll need to save them manually now.

grails 1.3.x:

mockDomain(Person, [new Person(name: "Bob")])

If you keep the old style of domain mocking through mockDomain you need to ensure that you aren’t calling it twice on the same class (say in setUp and then again in a test method, or in a superclass and a subclass). This causes really hard to find test pollution errors downstream with the grails 2.0.3 code. It appears that it’s messing with the metaClass again and re-mocking it, but then when the test is done not fully cleaning up after both metaClass modifications. I consider this a bug as mockDomain should be idempotent; if you’ve already mocked a class calling mockDomain again shouldn’t remock the class, but this is what we have now.

Grails 2.0.x:

class MyTests {
    @Test public void testPerson() {
        new Person(name: "Bob").save(failOnError: true)

One caveat is that validation is now enforced on these objects, you can either pass in validate:false on the object, or use the grails BuildTestData plugin to create your valid objects:

class MyTests {
    @Test public void testPerson() {
        Person.build(name: "Bob")

The Build Test Data plugin has recently been enhanced to work with grails unit tests, and I recommend using it’s @Build annotation in place of @Mock in pretty much every case. It just makes things so much easier by building fully valid objects automatically for you.

Controller Unit Tests in Grails 2


grails 1.3.x:

mockConfig """

Grails 2.0.x:



grails 1.3.x:

mockParams.foo = "bar"

Grails 2.0.x:

params.foo = "bar"

So you can replace all mockParams with just params.


grails 1.3.x:

assertEquals "Expected Message", mockFlash.error

Grails 2.0.x:

assert "Expected Message" == flash.error

So you can replace all mockFlash with just flash.

renderArgs and redirectArgs

The very useful renderArgs and redirectArgs mock objects are gone in grails 2.0 and I find the new way of testing without them to be harder than it was.


grails 1.3.x:

assert 'edit' == renderArgs.view

Grails 2.0.x:

assert view.endsWith('/edit')


grails 1.3.x:

assertEquals 'person', redirectArgs.controller
assertEquals 'show', redirectArgs.action
assertEquals personInstance.id, redirectArgs.id

Grails 2.0.x:

assert response.redirectUrl.endsWith("/person/show/${personInstance.id}")

(or just use contains or .split(‘/’) to find parts of the url)


grails 1.3.x:

assert renderArgs.model.person.id == person.id

Grails 2.0.x:

assert model.person.id == person.id

This seems to work unless you’re rendering a template in your controller rather than a view. In that case, the model isn’t populated and cannot be accessed as far as I can tell. This is documented in GRAILS-8659 which is marked as “Not a Bug”, something I disagree with as the workaround is much more painful.

The workaround, per Graeme Rocher on that ticket is to mock the template rendering:

views['/test/_bar.gsp'] = 'Hello ${name}'
assert response.contentAsString == "Hello John"

Where controller:

def renderTemplate() {
    render template:"bar", model:[name:"John"]

The naming convention of what you set in the map should be in the format “/person/_editAddress.gsp”, where you’re in the PersonController and the template name is editAddress but there were a few situations where I had trouble getting this right.

If you want to be sure what value to set the path to in the views map, set a breakpoint in RenderDynamicMethod.java around like 319 where it does this check:

String templateUri = webRequest.getAttributes().getTemplateURI(controller, templateName);

The value of that templateUri is what you want to set in your views map.


grails 1.3.x:

assert "bar" == redirectArgs.params.foo

Grails 2.0.x:

// strip off everything up till the ? that marks the start of the query string
def params = WebUtils.fromQueryString(response.redirectUrl.find(/[^?]*$/))
assert "bar" == params.foo

gross. This really should be better and usability has regressed on this.


grails 1.3.x:

assertEquals 404, mockResponse.status
assertEquals "foo bar baz", mockResponse.contentAsString

Grails 2.0.x:

assert 404 == response.status
assertEquals "foo bar baz", response.contentAsString

So you should be able to find/replace mockResponse with just response


grails 1.3.x:


grails 2.0.x:

In many cases, this can simply be deleted. If it’s either the class under test (the class you are doing a @TestFor on) or something you’ve already got in a @Mock or a @Build class level annotation, it already has logging.

For those classes that need logging, but are not already mocked, you can use the grails.test.MockUtils.mockLogging static method. Just add this static import to your class to get the same behavior:

import static grails.test.MockUtils.*

If it’s a collaborator that isn’t already mocked out, you can use the mockFor method as described in the mocking collaborators section of the grails user docs.

def foo = mockFor(Foo, true) // true here is "loose" mocking, otherwise you need to `demand` method calls for them to not fail.

Overall, I’m much happier with grails 2.0. It has a number of great features and speed improvements that make it a worthwhile upgrade. I complain above about a few places where I think testing is not as easy as it was, but most of these could be remedied with a few patches to grails-core.