Helpful error messages in Grails

Currenttly, I'm in the process of building a Grails application. While I've built several prototypes/quick hacks, this is actually the first 'real' application I'm building. "So", I thought, "if this is a real application, I'm in need of some real tests!". When you're in the normal flow of developing a Grails application, everything goes so fast, you almost forget about writing the tests. So I decided to do it a bit differently, and do it just like in Java: do it TDD!

The problem

I've currently created a small domain model. On of the classes in the domain is called a task, which looks something like this:

class Task {
    String title
    String description

    static constraints = {
        title(blank: false)
        description(blank: true)
    }
}

As you can see here, I've create a small class with 2 properties, of which the description is optional. Now, I'd like to know if my validation is correct, so I've created a testcase.

In Grails, there's a difference between integration testing and unit testing. The integration tests startup the whole Grails container, inject your domain classes with the proper GORM methods, etc, etc, while the unit tests don't: they're just quick functional tests.

The testcase looks like this:

void testCreateTask() {
  assertNotNull new Task(description: 'This is a task').save()
}

Running this test ('grails run-app') gives me the following output:

-------------------------------------------------------
Running 1 Integration Test...
Running test com.xebia.domain.TaskTest...
--Output from testCreateProject--
testCreateTask...FAILURE
Integration Tests Completed in 719ms
-------------------------------------------------------

Hmm, my test fails. But why? Oh, I see, I forgot to add a title to my task. But where is my error message? Grails validation knows what went wrong, but why do I only see a FAILURE in the log?

What happens, is that Grails creates a HTML report containing the tests and their respective output. When opening the report, you see something like the picture below:

Grails HTML report

Alas, no details on why the test fails! The only information given by the stacktrace is the line on which the test failed: 23, but no further information on why the test failed.


N/A
junit.framework.AssertionFailedError
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
at groovy.lang.ExpandoMetaClass.invokeMethod(ExpandoMetaClass.java:946)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:77)
at com.xebia.domain.TaskTest.testCreateTask(GrailsProjectServiceTest.groovy:23)

Like I said above, in this case it's easy: just add a title, and the test will work. But when using some more complex behaviour, it might be a bit harder to find out what went wrong. Because I couldn't find a proper solution in the Grails documentation, I decided to create my own solution.

Solution

What I did to 'fix' this problem, was to define a new method, named 'assertValid'. This method (closure actually) checks if the supplied argument is valid, and if it isn't, it will collect the error messages associated with the object and display then in an assert statement.

This results in the following code:

GroovyTestCase.metaClass.assertValid << { object ->
  println "assertValid"
  def validate = object.validate()
  def message = object.errors.allErrors.inject('') {str, error ->
    str + "Field error in object ${error.objectName} on field ${error.field}: rejected value [${error.rejectedValue}];"
  }

  assertTrue message, validate
  assertNotNull object.save()
}

What I did here, was use Groovy's ExandoMetaClass facility here to dynamically add a method to GroovyTestCase, which is the class all tests extend from. The assertValid method is added to it, and is now available for use in all instances of the GroovyTestCase. This means I can now do the following:

void testCreateTask() {
  Task task = new Task(description: 'This is a task')
  assertValid task
}

Running this will ofcourse still produce an error, but instead of giving an 'N/A' as message, the test report will now display the following:


Field error in object com.xebia.domain.Task on field title: rejected value [null];

Conclusion

So, by leveraging some of Grails Spring power, and the Groovy MetaClass facility, it was quite easy to get some detailed information about the failures in the Grails domain binding, and in a very reusable way! I hope you've found this blog useful, and if you've any comments, don't hessitate and let me know!

Comments (2)

  1. Age Mooy - Reply

    May 4, 2008 at 1:24 pm

    Nice workaround that shows off the metaclass magic. But this a basically a very irritating bug in Grails. Have you submitted a bug or a feature request ?

  2. Erik Pragt - Reply

    May 4, 2008 at 4:06 pm

    Hi Age, while I agree it's suboptimal, I find it a bit farstreched to call it a bug. It just more work to get the right information.

    I will post this a feature request when I'm sure I haven't overlooked anything in the Grails documentation.

Add a Comment