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
1
| |
the annotation needs to be
1
| |
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:
1
| |
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
mockConfigmethod 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 theconfigproperty. - Remove
mockDomaincalls & add a@TestForor@Mockannotation to the test class. Use@TestFor(MyDomainClass)for the domain class’ own tests and@Mock(MyDomainClass)when the domain class is a collaborator.@Mockaccepts 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>todomainInstance.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
allowedMethodsdeclaration must set therequest.methodor 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
@Mockis used. It seems like even when assigning ids on construction with code likenew 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. grailsApplicationis 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
@TestForcan 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
@TestForthat modifying the metaclass of thetaglibfield of the test class didn’t affect tests using applyTemplate. However, we just had to change fromtaglib.metaClass.blahtoMyTagLib.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:
1 2 3 4 5 | |
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
selectelements 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: truefailed to save. The workaround is to simply duplicate the constraint in the child class (yes, it’s ugly). - GRAILS-8377
grails test run-appfails withError 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.