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 Grails 2 Testing Documentation
- Peter Ledbrook’s “Upgrade to Grails 2″ blog post
- The Grails 2 “Upgrading From Previous Versions of Grails” section of the user guide.
- Rob Fletcher’s Upgrading to Grails 2: Part 1
- This google plus thread on people’s experiences upgrading to grails 2
- GRAILS-7617 – TestFor(ControllerClass) annotation doesn’t provide renderArgs
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, `Mixin`s, 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@AfterMUST be declaredpublic voidor 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 oldextends GrailsUnitTestCase - Integration tests should also never extend
GrailsUnitTestCase, they should instead extendGroovyTestCaseand just useassert - 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-appdoes 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 theJUnit4GrailsTestType.doPreparemethod (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 prependgrails test-appon 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
mockDomain
grails 1.3.x:
class MyTests {
...
mockDomain(Person)
mockDomain(Vehicle)
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:
@TestMixin(GrailsUnitTestMixin)
@Mock([Person])
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:
@TestMixin(GrailsUnitTestMixin)
@Build([Person])
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
mockConfig
grails 1.3.x:
mockConfig """
grails.foo.bar='baz'
"""
Grails 2.0.x:
grailsApplication.config.grails.foo.bar='baz'
mockParams
grails 1.3.x:
mockParams.foo = "bar"
Grails 2.0.x:
params.foo = "bar"
So you can replace all mockParams with just params.
mockFlash
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.
renderArgs.view
grails 1.3.x:
assert 'edit' == renderArgs.view
Grails 2.0.x:
assert view.endsWith('/edit')
redirectArgs
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)
renderArgs.model
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}'
controller.renderTemplate()
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.
redirectArgs.params
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.
mockResponse
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
mockLogging
grails 1.3.x:
mockLogging(Foo)
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.*
...
mockLogging(MyService)
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.
Probably worth mentioning Rob Fletcher’s blogpost on upgrading to Grails 2, which does contain a bunch of unit test tips – http://blog.freeside.co/blog/2011/11/29/grails-2-upgrade-part-1/ – and was written more than 10 days ago…
Excellent. Wish it would have been before when I tried to run my test cases in Grails 2.0 :) Nice stuff…..
@Tomas I missed Rob’s post in my googling, but it definitely looks like one I would have gotten use out of 10 days ago :), I’ll add it above. Thanks for the link!
[...] Upgrading to Grails 2 Unit Testing [...]
[...] Upgrading to Grails 2 Unit Testing [...]
[...] my recent upgrade of an application from grails 1.3.7 to grails 2.0.3 I hit quite a few tests that weren’t properly cleaning up after themselves that needed to be [...]
Hello Ted,
have you had Problems with grails integration tests, too? I have no informations found about how to use integration tests!
Best regards!
Heiko