Friday, July 11, 2008

Groovy Threads and MetaClass example

* Update - the code has been updated. The original test was incorrect and was producing a false positive. What you see now is the correct way.

It's been awhile since I have been able to play around with grails/groovy. Now, instead of using a pretend app to learn grails/groovy, I have teamed up with Jeff Black, Chad Gallemore, and Sam Jones (fellow office co-workers here in Joplin, MO) to rewrite an existing small internal java webapp using grails. It's a perfect application for grails and so far we are loving it.

Early on, we needed to figure out 2 things:
1) Groovy way of creating Threads
2) Writing an integration test for a Groovy Service

Groovy Threads
Below is a summary of our service that shows how to start new threads in groovy.

class ProjectService {
def discover() {
Project.list().each {
Thread.startDaemon {
jobService.update(it)
}
}
}
}
Service Integration test with MetaClass
Here is the integration test I wrote that tests the above Service.
void testEmptyProject() {
def called = false

Project.metaClass.static.list = {[]}
Thread.metaClass.static.startDaemon = {Closure c -> c.call()}
JobService.metaClass.update = {called = true}
new ProjectService(jobService: new JobService()).discover()

assertFalse('updateJobs should not have been called', called)
}

The first missing key for me was the static keyword on metaClass since in the service I am calling Project.list() and Thread.startDaemon(). The second mystery was how to mock out Thread.startDaemon() since there could be, and was, a race condition between the update closure setting called = true and my assertFalse.

Thanks Chad for the suggestion of using metaClass. I also got a lot of help from Glenn Smith's blog about testing controllers and Dustin's groovy thread example.

2 comments:

James Lorenzen said...

Update.
Looks like there is a better way to define the closure when overriding methods. I was struggling recently to get this to work and found this article that helped me solve my problem: http://www.ibm.com/developerworks/java/library/j-pg06239/index.html

Basically instead of defining it like this:
Project.metaClass.static.list = {[]}

A better way is:
Project.metaClass.static.list = {->
[]
}

jlorenzen said...

If you run into issue using metaClass to mock out classes in a test, and the test has multiple test methods that mock out the same class, take a look at this article if the method isn't getting replaced. http://stackoverflow.com/questions/12178251/instance-metaclass-changes-leaking-from-instance-to-instance