Skip to main content

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 :-)

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.
Unknown 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

Popular posts from this blog

Running your own one person company

Recently there was a post on PuneTech on mom's re-entering the IT work force after a break. Two of the biggest concerns mentioned were : Coping with vast advances (changes) in the IT landscape Balancing work and family responsibilities Since I have been running a one person company for a good amount of time, I suggested that as an option. In this post I will discuss various aspects of running a one person company. Advantages: You have full control of your time. You can choose to spend as much or as little time as you would like. There is also a good chance that you will be able to decide when you want to spend that time. You get to work on something that you enjoy doing. Tremendous work satisfaction. You have the option of working from home. Disadvantages: It can take a little while for the work to get set, so you may not be able to see revenues for some time. It takes a huge amount of discipline to work without a boss, and without deadlines. You will not get the benefits (insuran

Some thoughts on redesigning education

Some time back I read a blog post on redesigning education. It asked some very good questions. Stuff which I had been thinking of myself. I left my thoughts on the blog, but I would also like to start a conversation around these ideas with those who read this blog as well. I would like to know what other people think of the issue of redesigning (college) education. I have often thought about how college education can be improved. To answer this question, we first have to ask a very basic question. What is the purpose of education? To me, we need education for 3 things: To learn more about the world around us To lead positive constructive lives To earn a good living / fulfill our ambitions I think education has to a large extent evolved to fulfill #3 (with a bias towards earning a comfortable living). The semester system, along with multiple choice tests, and grading, has made our education system into an assembly line. Students are pushed into the assembly line, given classes, admini