Thursday, June 23, 2011

Unit Testing Criteria Queries in Grails

I was working on a Grails project and came to a section that I needed to do pagination and filtering and the best way for me to do that was by using a criteria query.  A criteria query let me set the max rows returned, the offset and also allowed me to add other criteria.  So I started to put together my criteria query and being a TDD person, I wanted to write tests.  Unfortunately the mocking in grails did not allow me to do this easily and I really didn't find much on the web to help me with it.  So I started some trial and error and came up with some approaches.

The code I was trying to test was

def criteria = AudioCapture.createCriteria()
def results = criteria.list(max: params.max, offset: params.offset){
   if(params.sessionId){
        eq("sessionId", "${params.sessionId}")
    }
    if(params.startDate && params.endDate){
        between("dateStamp", new Date(params.startDate), 
          new Date(params.endDate))
   }
}


I was struggling with how to test if the between and eq were called.  I wasn't sure how to do this, but then I remembered the beauty of groovy meta programming.

The first thing I did was to add an eq method to the controllers metaclass that would assert that the field was correct and that the value was correct.  I also added a field to verify that the method was called.  It looked like this

controller.metaClass.eq = {def fieldName, def fieldValue ->
    assert fieldName = "foo"
    assert fieldValue = "bar"
    eqCalled = true
}

I did the same for the between method.  Lastly I used the standard mockFor to mock the createCriteria method and to demand that it was called with the right max and offset values.  This demand clause needed to do one more thing.  It needed to execute the closure that was passed to it.  Otherwise the closure was never executed.  This code looked like this

def myMock = mockFor(FooClass)
myMock.demand.static.createCriteria(1..1){
    return [list: {def a, def b ->
        assert a.max == 50
        assert a.offset == 55
        assert b != null
        b()
        return [totalCount: 5]
    }]
}

This set up the createCriteria to return an map that had a list method on it to simulate the criteria object that is worked with.  This list method takes to parameters, a map and a closure.  The values in the map are checked against the expectations, the closure is executed.  Lastly a map is returned.  The map will hold any values that are needed.  In this case I accessed results.totalCount, so I wanted to make sure there was a value there.

There you have it a way to test the criteria queries.  If you had multiple eq calls, the function might have to get a bit complicated but this works.  Hopefully the improvements to unit testing in 1.4 will make a lot of this unnecessary.  Until then I hope this helps people so they don't have to do the same trial and error I did.