Ad-Hockery

ad-hockery: /ad·hok'@r·ee/, n.
Gratuitous assumptions... which lead to the appearance of semi-intelligent behavior but are in fact entirely arbitrary. Jargon File

Using GMock to complement Grails mockDomain

Since Grails 1.1 we’ve had pretty good unit testing support via GrailsUnitTestCase and its sub-classes. The mockDomain method is particularly useful for simulating the various enhancements Grails adds to domain classes. However, there are some domain class capabilities, such as criteria queries and the new named queries, that can’t really be simulated by mockDomain.

So assuming we’re trying to unit test a controller that uses criteria methods or named queries on a domain class how can we enhance the capabilities of mockDomain? One of my favourite Groovy libraries is GMock which I use in preference to Groovy’s built in mock capabilities. One of its really powerful features is the ability to use partial mocks, i.e. to mock particular methods on a class whilst allowing the rest of the class to continue functioning as normal. This means we can layer a mocked createCriteria, withCriteria or named query call on to a domain class that is already enhanced by mockDomain.

First off you need to add the GMock dependency to your BuildConfig.groovy. Since GMock supports Hamcrest matchers for matching method arguments you’ll probably want those as well:

dependencies {
    test "org.gmock:gmock:0.8.0"
    test "org.hamcrest:hamcrest-all:1.0"
}

If you’re using an earlier version of Grails you’ll need to just grab the jar files and put them in your app’s lib directory.

Then in your test case you need to import GMock and Hamcrest classes and add an annotation to allow GMock to work:

import grails.test.*
import org.gmock.*
import static org.hamcrest.Matchers.*

@WithGMock
class MyControllerTests extends ControllerUnitTestCase {

Adding criteria and named query methods is now fairly simple:

Mocking a withCriteria method

def results = // whatever you want your criteria query to return
mock(MyDomain).static.withCriteria(instanceOf(Closure)).returns(results)
play {
    controller.myAction()
}

Breaking this example down a little

  1. mock(MyDomain) establishes a partial mock of the domain class.

  2. instanceOf(Closure) uses a Hamcrest instanceOf matcher to assert that the withCriteria method is called with a single Closure argument (the bit with all the criteria in).

  3. returns(results) tells the mock to return the specified results which here would be a list of domain object instances.

In this example we’re expecting the withCriteria method to be called just once but GMock supports more complex time matching expressions if the method may be called again.

Mocking a createCriteria method

The withCriteria method returns results directly but createCriteria is a little more complicated in that it returns a criteria object that has methods such as list, count and get. To simulate this we’ll need to have the mocked createCriteria method return a mocked criteria object.

def results = // whatever you want your criteria query to return
def mockCriteria = mock() {
    list(instanceOf(Closure)).returns(results)
}
mock(MyDomain).static.createCriteria().returns(mockCriteria)
play {
    controller.myAction()
}

This is only a little more complex than the previous example in that it has the mocked list method on another mock object that is returned by the domain class' createCriteria method. mock() provides an un-typed mock object as we really don’t care about the type here.

Mocking a named query

For our purposes named queries are actually pretty similar to createCriteria.

def results = // whatever you want your criteria query to return
def mockCriteria = mock() {
    list(instanceOf(Closure)).returns(results)
}
mock(MyDomain).static.myNamedQuery().returns(mockCriteria)
play {
    controller.myAction()
}

Some other examples:

Mocking a named query with an argument

mock(MyDomain).static.myNamedQuery("blah").returns(mockCriteria)

For simple parameters you don’t need to use a Hamcrest matcher - a literal is just fine.

Mocking a withCriteria call using options

You can pass an argument map to withCriteria, e.g. withCriteria(uniqueResult: true) { /* criteria */ } will return a single instance rather than a List. To mock this you will need to expect the Map as well as the Closure:

def result = // a single domain object instance
mock(MyDomain).static.withCriteria(uniqueResult: true, instanceOf(Closure)).returns(result)

Mocking criteria that are re-used

It’s fairly common in pagination scenarios to call a list and count method on a criteria object. We can just set multiple expectations on the mock criteria object, e.g.

def mockCriteria = mock() {
    list(max: 10).returns(results)
    count().returns(999)
}
mock(MyDomain).static.myNamedQuery().returns(mockCriteria)

The nice thing about this technique is that it doesn’t interfere with any of the enhancements mockDomain makes to the domain class, so the save, validate, etc. methods will still work as will dynamic finders.

Be aware however, that what we’re doing here is mocking out the criteria queries, not testing them! All the interesting stuff inside the criteria closure is being ignored by the mocks and could, of course, be garbage. Named queries are pretty easy to test by having integration test cases for your domain class. Criteria queries beyond a trivial level of complexity should really be encapsulated in service methods or named queries and integration tested there. Of course, GMock then makes an excellent solution for mocking that service method in your controller unit test.

Web Statistics