Ad-Hockery

ad-hockery: /ad·hok'@r·ee/, n.
Gratuitous assumptions... which lead to the appearance of semi-intelligent behavior but are in fact entirely arbitrary. Jargon File

Upgrading to Grails 2: Part 1

We’ve recently spent some time ensuring that our application is forwards-compatible with the upcoming Grails 2. Our app is broadly split into the application itself and a large plugin that forms the core of our CMS. We upgraded the plugin first as it contains the core domain classes but is also a slightly smaller task to tackle. I’ll write up our findings when we’ve completed upgrading the application itself in a follow-up to this post.

Although it took a little while I found the upgrade less painful than some previous versions. It feels like Grails is really maturing so there are fewer fundamental breaking changes and more in the way of new features. The bulk of the changes we had to make were to unit tests which are vastly improved in Grails 2. That said, I can see some of the things we ran into stumping people so I thought a write-up would make a useful reference. Some of these items may already be covered by the upgrading section of the Grails user guide but some, particularly around plugins are probably new.

Compilation

Firstly we needed to do to just get the build working to the point where tests would run.

Upgrade Spock

Version 0.6-SNAPSHOT of the Spock plugin is required for compatibility with Groovy 1.8.

This also required us to update @Unroll annotations to use Closure parameters. Instead of

@Unroll("#a plus #b should equal #x")

the annotation needs to be

@Unroll({"$a plus $b should equal $x"})

Whilst this is a bit of a chore there are some advantages to this format as you can reference properties and call methods on the parameters right in the annotation.

Before the tests would run we got a compilation error:

The return type of java.lang.Object mockDomain(java.lang.Class) in com.sky.cms.AssetSpec is incompatible with void mockDomain(java.lang.Class) in grails.plugin.spock.UnitSpec`

There’s some kind of clash between the method signatures of mockDomain in Spock’s UnitSpec and Grails core. We’d intended to switch to the new annotation-based unit test support anyway but apparently we needed to do so right away. On the surface this just meant making our specs extend Specification instead of UnitSpec and introducing the new @TestFor annotation. The other changes we had to make to get our tests running successfully are covered below.

Configuration changes

Configuration-wise all we had to do was to change DataSource.groovy to reference H2 rather than hsqldb. Because we were just dealing with the CMS plugin and its test harness we hadn’t modified the default file in any way so we just replaced it with the new one from Grails 2 with:

cp $GRAILS_HOME/src/grails/grails-app/conf/DataSource.groovy grails-app/conf/

Unit tests

The bulk of the time we spent upgrading was spent on unit tests. This is understandable since Grails 2 has made some significant enhancements in unit test support. Most of the changes are pretty straightforward but there are a couple of gotchas here.

  • The mockConfig method was always a bit awkward (as anyone who has tried to assign the path to a File object to a config key on a Windows machine will attest) and has been removed. Unit tests now get a genuine GrailsApplication instance injected by default if using one of the new annotations and you can directly assign values to the config property.

  • Remove mockDomain calls & add a @TestFor or @Mock annotation to the test class. Use @TestFor(MyDomainClass) for the domain class' own tests and @Mock(MyDomainClass) when the domain class is a collaborator. @Mock accepts multiple classes if you need it.

  • We had a number of assertions about domain validation errors that we had to change from domainInstance.errors.<fieldName> to domainInstance.errors.getFieldError('<fieldName>').code. The latter is more long winded but has the advantage of using the same API as when you deal with Spring Errors instances in application code.

  • Tests for a controller that has an allowedMethods declaration must set the request.method or they get a 405: Method Not Allowed response.

  • Domain class validation is evaluated on mock domain instances. Often this isn’t actually desirable. Tests cluttered with data setup that is only required to satisfy domain constraints are harder to maintain. To get round this you can use .save(validate: false).

  • Domain class event handlers such as beforeInsert are not triggered unless the session is flushed. In the few tests this affected we just had to change .save() to .save(flush: true).

  • Assigned ids are wiped from domain object instances when @Mock is used. It seems like even when assigning ids on construction with code like new Pirate(id: 1234, name: "Edward Teach") the id will be null until the save method is called. This wasn’t a big deal for us & mostly meant getting rid of a few magic numbers from test code.

  • grailsApplication is not wired into domain instances like it is into controllers and tag libs when using @TestFor.

  • We ran into some issues with tests for property editors where we were setting up mock a request variable. We had a NullPointerException thrown from domain class constructors after doing so. Using the request property provided by @TestMixin(ControllerUnitTestMixin) instead fixed things.

  • We converted our old tag lib tests to use @TestFor(TagLibClass) and the new applyTemplate method. See the relevant section of the Grails user guide for more details on this.

  • Tag libs using @TestFor can actually find and render GSP templates when they call render. This means you might get more realistic output than your test was previously expecting!

  • We found that when testing tag libs with @TestFor that modifying the metaclass of the taglib field of the test class didn’t affect tests using applyTemplate. However, we just had to change from taglib.metaClass.blah to MyTagLib.metaClass.blah.

Plugins

Joda Time

We upgraded the Joda-Time plugin to version 1.3. This also meant we had to add the Hibernate persistence library to the dependencies section of BuildConfig as documented here.

Resources

The Resources plugin currently (up to at least version 1.1.1) has a bug that means it does not generate resources properly in anything other than dev mode. There is a workaround; just add the following to BootStrap:

def grailsResourceProcessor

def init = {
    grailsResourceProcessor.updateDependencyOrder()
}

Geb

We upgraded Geb to version 0.6.1 and the Selenium drivers to version 2.13.0. The only issues we ran into were:

  • An additional dependency is required to handle select elements properly (installation details are handily included in the report for any test that fails due to this).

  • Setting values on input type="file" elements no longer worked. I’ve already fixed this issue and it should be in the next Geb release. See GEB-152 for more information.

Other Libraries

We had one test that used GMock. Unfortunately it is not currently compatible with Groovy 1.8. For us this wasn’t a big deal as we could easily drop GMock but some codebases may be more invested in it.

Actual Grails bugs

We only found a couple of problems with Grails itself both of which I’ve raised on the Grails bug tracker:

  • GRAILS-8376 Constraints on superclass associations are not inherited properly in mock domain instances. This bit us when instances of a child class of a superclass that had an association with nullable: true failed to save. The workaround is to simply duplicate the constraint in the child class (yes, it’s ugly).

  • GRAILS-8377 grails test run-app fails with Error loading plugin manager: GebGrailsPlugin. This is kind of an edge case. We were only trying to run the application in test mode to help figure out the problem with resource processing mentioned above.

Web Statistics