Monday, 22 April 2013

When Grails Attacks

I've been pretty harsh on Grails before but I'm starting to appreciate some of its charms now. Of course I still think Play! 2.1 is even better but must admit there are fewer OMFGWTFBBQ?!?! moments with Groovy compared to Scala. That might just be me and my functional-programming newb-ness, or it might just be that while Groovy is a new front door on Java, Scala is a new top floor, roof and swimming pool out the back...

 Anyway.

Innocently trying to run some unit tests on an existing set of Grails controllers. Everybody else seems to be running them fine, but I get:

| Running 9 unit tests... 1 of 9
| Failure:  testThatAAABBDDD(BrokenAssTests)
|  java.lang.NullPointerException: Cannot set property 'method' on null object
 at BrokenAssTests.setup(BrokenAssTests.groovy:155)
| Running 9 unit tests... 2 of 9
| Failure:  testThatEEEFFGGG(BrokenAssTests)
|  java.lang.NullPointerException: Cannot set property 'method' on null object
 at BrokenAssTests.setup(BrokenAssTests.groovy:155)

The setup() method in question:
  @Before
  public void setup() {
    request.method = 'GET'
  }

 Same Groovy version, same Grails version, very similar Java version (1.7.0_11 vs 1.7.0_21, hardly a biggie), so what's going on?

I'm running Ubuntu 12.04, everyone else is on Macs (a story for another day).

A great deal of digging and Googling later, and here's what's going on:


  • Grails 2.0 introduced the TestFor annotation which automagically mixes in the ControllerUnitTestMixin  
  • As one of its many magic tricks, the ControllerUnitTestMixin adds mocked request and response objects into the test scope
  • This magic is performed in method bindGrailsWebRequest() which if you persue the documentation, has the JUnit 4 @Before annotation applied
  • The only ordering guarantee about @Before evaluation is "superclass comes before subclass"
  • But a mixin is NOT a superclass!
  • And so the order of @Before evaluation when using Grails' test mixins is essentially OS/JVM dependent
In my case, I was the unlucky "canary" on the "different" platform who encountered the bug. The "fix", such as it was, was manually calling the mixin method just-in-time in "our" setup() method:
  @Before
  public void setup() {
    if (!request) {
      bindGrailsWebRequest()  // Make sure request is visible - the ordering of @Befores is not guaranteed :-(
    }
    request.method = 'GET'
  }
I don't like it much, and hopefully the next version of Grails rectifies this, but for me it's just another one of those "too many layers of magic" problems that in my experience seems to afflict the Groovy world more than others.

No comments:

Post a Comment