Grails Unit Testing: How to mock a closure
Ian | August 29, 2008I have found numerous resources on testing in grails, unit testing and Using MockFor and StubFor in groovy. However, given that closures are one of the key features of groovy, I found very little on how to accommodate them in testing.
The following is a simple controller with a list action that returns a set of Customer domain objects from a database and this action applies some simple filter criteria (thanks to this post I came across):
def list = {
def items
if(!params.max) params.max = 10
if(!params.sort) params.sort = "lastUpdated"
if(!params.order) params.order= "desc"
if (params?.filter) {
def criteria = Customer.createCriteria()
items = criteria.list(params) {
or {
ilike ('surname', "%${params.filter}%")
ilike ('forename', "%${params.filter}%")
ilike ('emailAddress', "%${params.filter}%")
}
}
}
else {
items = Customer.list (params)
}
render (view: 'list', model: [ customerList: items, filter:params.filter ], params: params)
}
This action uses the Hibernate Criteria Builder to construct the query.
When unit testing Grails does not inject any of the dynamic methods and so these must be provided for. For this I covered the dynamic controller methods in the set up and tear down operations (thanks wholly to Glen Smith’s MockFor(March): Unit Testing Grails Controllers…
def redirectParams
def renderParams
def params
/** Setup metaclass fixtures for mocking. */
void setUp() {
params = [ : ]
CustomerController.metaClass.getParams = { -> params }
redirectParams = [ : ]
CustomerController.metaClass.redirect = { Map args -> redirectParams = args }
renderParams= [ : ]
CustomerController.metaClass.render = { Map args -> renderParams = args }
}
/** Remove metaclass fixtures for mocking. */
void tearDown() {
def remove = GroovySystem.metaClassRegistry.&removeMetaClass
remove CustomerController
}
The dynamic methods for the domain class I covered in the test where they were used.
void testNoFilterReturnsList() {
def testItems = [ 'x', 'y', 'z' ]
params['filter'] = null ;
// mock the static list and count methods
Customer.metaClass.static.list = { Map params -> testItems }
CustomerController cc = new CustomerController()
cc.list()
assertNull renderParams.model.filter
assertEquals testItems, renderParams.model.customerList
}
This could hardly be simpler and is covered in much more detail elsewhere. Where I ran into difficulty was testing the the criteria was applied.
I wanted to provide a mock object for the org.hibernate.Criteria object returned for the domain.
def customerCriteria = new MockFor(org.hibernate.Criteria)
Defining expectations on the mock object is straight forward too, just demand it!
customerCriteria.demand.list { -> testItems }
Except that what ever I seemed to try would not match the signature of the call to list that I was trying to do in the filter test above.
Despite being a little elusive, the answer was simple enough. The closure defining the search criteria is a parameter to the call. There must be a list method declared with a signature along the lines of
def list (Map params, Closure criteria)(At least, that’s what I had to do in a test class to mimic that results I was seeing.)
So, to define a demand to match that… well, just treat it as such.
void testFilterAppliedToCriteria() {
def testItems = [ 'x', 'y', 'z' ]
params['filter'] = 'search on something' ;
def customerCriteria = new MockFor(org.hibernate.Criteria)
customerCriteria.demand.list { Map params, Closure cls -> testItems }
Customer.metaClass.static.createCriteria = { org.hibernate.Criteria }
customerCriteria.use{
CustomerController cc = new CustomerController()
cc.list()
assertEquals params.filter, renderParams.model.filter
assertEquals testItems, renderParams.model.customerList
}
}
I continue to stumble through, though I’m becoming more convinced that I don’t know what I am doing.






[...] that this can be done as a closure to
englishteeth.co.uk » Groovy on Grails and my “Test First” impasse | September 4, 2008[...] that this can be done as a closure to the list method of hibernate criteria and having figured out how to mock a closure I can assert that a filter has indeed been [...]
Were you able to get this to work? When I
Justin | October 2, 2008Were you able to get this to work? When I tried something similar, I found that my stubbed version of the list call (Customer.metaClass.static.list = { Map params -> testItems }) was never invoked and instead the real one would be called.
I’m curious to know whether you got this to work.
Hi Justin, the above definitely worked for me. Your stubbed version
Ian | October 3, 2008Hi Justin, the above definitely worked for me.
Your stubbed version of the list method - is it definitely the list method of your domain object that the controller is accessing?
What I mean is, if it’s not the same signature and does not match the meta class method you’ve replaced, you will just get the real one being invoked.
Thanks for the tip. This was a big help. The
Pete McKinstry | July 27, 2009Thanks for the tip. This was a big help.
The one modification I made was to have the closuure return a PagedResultList (same as the CriteriaBuilder.list() method). I was using the totalCount property in the controller to enable pagination on the UI so it was necessary.
Again, thanks a bunch.
-pgm