Saturday, May 30, 2009

Testing Groovy domain classes

If you are trying to test Grails domain class constraints by putting your unit test cases in the 'test/unit' directory, then your tests will fail because the domain objects will not have the 'valdate' method. This can be resolved in two ways:
  1. Place the test cases inside test/integration (which will slow things down)
  2. Use the method 'mockForConstraintsTests(Trail)' to create mock method in your domain class and continue writing your test cases in 'test/unit'


What follows is some example code around this finding.

I am working on a Groovy on Grails project for a website to help programmers keep up and refresh their skills.

I started with some domain classes and then moved on to write some unit tests. When we create a Grails project using
grails create-app
,
it creates several directories, one of which is a directory called 'test' for holding unit tests. This directory contains two directories, 'unit', and 'integration' for unit and integration tests respectively.

Now begins an interesting journey with writing unit tests in Groovy. I wanted to write tests for my domain classes. A simple domain class like the one I have below can contain only properties and constraints for those properties. This is what Trail.groovy looks like.


class Trail {
String shortName
String name
String description

def hasMany = [learningObjects:LearningObject]

static constraints = {
shortName(maxSize:6, blank:false)
name(maxSize:75, blank:false)
description(maxSize:2048, blank:false)
}
}


I wanted to start writing unit tests to test the constraints. When Grails creates a domain class, it injects several methods in it at runtime , one of which is a method called 'validate'. This method is called before the domain object is saved and it will return a false if the domain object has violated any constraints. So, I created a simple unit test to test the constraints of Trail.groovy

This is what my initial test case looked like.


import grails.test.*

class TrailTests extends GrailsUnitTestCase {

def trail

protected void setUp() {
super.setUp()
trail = new Trail(shortName:'sname',
name:'Java 101',
description:'This is a basic Java course')
}

protected void tearDown() {
super.tearDown()
}

void testShortNameConstraints() {
assertTrue(trail.validate())
trail.shortName = 'thisisaverylongname'
assertFalse(trail.validate())
trail.shortName = ''
assertFalse(trail.validate())
}

//... further methods not shown
}


When I ran my tests using
grails test-app

(See how well Grails integrated testing), I got a bunch of errors that told me that the 'validate' method was not found. If you enjoy looking at stack traces... feast your eyes:

Testcase: testShortNameConstraints took 0.159 sec
Caused an ERROR
No signature of method: Trail.validate() is applicable for argument types: () values: []
groovy.lang.MissingMethodException: No signature of method: Trail.validate() is applicable for argument types: () values: []
at TrailTests.testShortNameConstraints(TrailTests.groovy:20)
at _GrailsTest_groovy$_run_closure4.doCall(_GrailsTest_groovy:203)
at _GrailsTest_groovy$_run_closure4.call(_GrailsTest_groovy)
at _GrailsTest_groovy$_run_closure2.doCall(_GrailsTest_groovy:147)
at _GrailsTest_groovy$_run_closure1_closure19.doCall(_GrailsTest_groovy:113)
at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:96)
at TestApp$_run_closure1.doCall(TestApp.groovy:66)
at gant.Gant$_dispatch_closure4.doCall(Gant.groovy:324)
at gant.Gant$_dispatch_closure6.doCall(Gant.groovy:334)
at gant.Gant$_dispatch_closure6.doCall(Gant.groovy)
at gant.Gant.withBuildListeners(Gant.groovy:344)
at gant.Gant.this$2$withBuildListeners(Gant.groovy)
at gant.Gant$this$2$withBuildListeners.callCurrent(Unknown Source)
at gant.Gant.dispatch(Gant.groovy:334)
at gant.Gant.this$2$dispatch(Gant.groovy)
at gant.Gant.invokeMethod(Gant.groovy)
at gant.Gant.processTargets(Gant.groovy:495)
at gant.Gant.processTargets(Gant.groovy:480)


After some Googling I realized that the 'validate' method is injected into domain objects by the Grails framework, and therefore it does not exist when we instantiate the domain object from test code. I later found that if a test is placed inside 'test/integration' then the test cases are created as the objects would have been created by the Grails framework. So the domain objects would have the 'validate' method. But I also read in another place (I cannot remember where), that if possible it is k better to mock these special methods instead of putting tests in the integration directory, since these tests take longer to instantiate and can slow down the overral running time. And I found in yet another place that there exists a method called 'mockForConstraintsTests(Trail)' which will inject the 'validate' method along with some other methods. So, I added this mock method to my setup after which I was able to run my tests successfully.


protected void setUp() {
super.setUp()
mockForConstraintsTests(Trail)
trail = new Trail(shortName:'sname',
name:'Java 101',
description:'This is a basic Java course')
}


The rest of the class is the same.

So to summarize, if you are trying to test constraints in Grails domain classes, you have two options:
  1. Place the test cases inside test/integration (which will slow things down)
  2. Use the method 'mockForConstraintsTests(Trail)' to create mock method in your domain class and continue writing your test cases in 'test/unit'
I will post more such episodes of how Google helped me resolve issues I faced while developing :-)

11 comments:

Anonymous said...

Hi,
i have the same problem when testing domain classes.
I tried your solution with mock object but i receive the same error.
The only way i was able to run the test was to move all the files into the integration folder.
have you find any other ways to solve this?

Parag said...

Hi, I have not found any other way. However, I am surprised that mocking did not insert that method.

Do you get the exact same error?

--
Parag
http://www.adaptivelearningonline.net

Anonymous said...

Yes,

i got the same error

No signature of method: Person.save() is applicable for argument types: () values: []

groovy.lang.MissingMethodException: No signature of method: Person.save() is applicable for argument types: () values: []
at PersonTests.testSomething(PersonTests.groovy:14)
at _GrailsTest_groovy$_run_closure4.doCall(_GrailsTest_groovy:203)
at _GrailsTest_groovy$_run_closure4.call(_GrailsTest_groovy)
at _GrailsTest_groovy$_run_closure2.doCall(_GrailsTest_groovy:147)
at _GrailsTest_groovy$_run_closure1_closure19.doCall(_GrailsTest_groovy:113)
at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:96)
at TestApp$_run_closure1.doCall(TestApp.groovy:66)
at gant.Gant$_dispatch_closure4.doCall(Gant.groovy:324)
at gant.Gant$_dispatch_closure6.doCall(Gant.groovy:334)
at gant.Gant$_dispatch_closure6.doCall(Gant.groovy)
at gant.Gant.withBuildListeners(Gant.groovy:344)
at gant.Gant.this$2$withBuildListeners(Gant.groovy)
at gant.Gant$this$2$withBuildListeners.callCurrent(Unknown Source)
at gant.Gant.dispatch(Gant.groovy:334)
at gant.Gant.this$2$dispatch(Gant.groovy)
at gant.Gant.invokeMethod(Gant.groovy)
at gant.Gant.processTargets(Gant.groovy:495)
at gant.Gant.processTargets(Gant.groovy:480)

I use grails 1.1.2 and java6

It's strange all the grails tutorials i found don't mention this problem.

Parag said...

I wish I could you, but I do not know what could be causing this problem.

--
Regards
Parag

Burak Dede said...

Try using "grails clean" and do saving again.

Nirav Assar said...

Parag, thanks for the post. It worked for me. I used mockForConstraintsTests(Race) for my Race object that I was testing for constraints. This worked in the unit test folder.

Parag said...

Hey Nirav, That's great. I am glad it helped.

Jason Lambert said...

For those who find this, make sure you are inheriting GrailsUnitTestCase and not the default GroovyTestCase to make mockForConstraintsTests work!

Parag said...

Thanks Jason.

Earlier someone posted a comment about the 'mockForConstraintsTests' method not working for them.

Perhaps they were using the GroovyTestCase instead of GrailsUnitTestCase.

mani said...

hey thnx dude....it helped a lot....and could you please give me some links where i can find useful grails tutorials or some books (not paid ofcourse :P) looking forward
thnx

Parag said...

Hi Mani, Thanks, I am glad the tutorial helped you.

It has been a while since I have worked on Grails, so I am not aware of the recent set of good tutorials or books. Their website has some links which might be useful to get started

http://grails.org/Tutorials