Grails Unit Testing - How to mock a closure

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

comments powered by Disqus