<< October 2009 | Home | December 2009 >>

Creating GMock expectations for methods that have a closure as a parameter

If like me you want to test as much of your grails/groovy application as possible then at some point you'll need to create a unit test for a method that accepts a closure as an argument.  GMock has the ability to use a 'match' closure to check to see if the arguments passed o your mock object are what you expect.  The example from their site is:

mockLoader.put("test", match { it > 5 }).returns("correct")
play {
    assertEquals "correct", mockLoader.put("test", 10)
}

The play closure calls the mockLoader instance's put method and the second parameter is passed to the match closure and if that closure returns true the value returned by the mockLoader instance will be the string 'correct'.

I had a method like this which I wanted to call and needed to mock out the deviceCriteria object and have the return value of the call to 'get' always return a fixed value so that other code in the test would behave correctly for the rest of the test.

protected Long countDevicesForLicense() {
    grails.orm.HibernateCriteriaBuilder deviceCriteria = Device.createCriteria()
    deviceCriteria.get{
        eq("license", this)
        projections {
            countDistinct "id"
        }
    }
}

The gmock test for this looks like this:

/**
 * Tests that a device cannot be added to a license that cannot have more devices added to it
 */
void testAddDevicesToFullLicense() {
    
    def deviceCountResult = 10
    
    def mockDevice = mock(Device)
    mockDevice.static.createCriteria().returns( mock(grails.orm.HibernateCriteriaBuilder) {
        get(match { arg ->
            arg instanceof Closure
        }).returns(deviceCountResult)
    })

    play {
        License license1 = new License(name: 'Full', maxDevices: 10)
        Device device1 = new Device(name: 'Test device')
        assertFalse license1.canAddDevice(device1)
    }
}    

Here, the canAddDevice() method internally calls the countDevicesForLicense() but as this is a unit test, not an integration tests no database is available so using a real HibernateCriteriaBuilder object would fail.  Thus we either have to mock out the database objects or create a partial mock.  And before anyone says anything - yes, a partial mock here would be better, but that's for another day.

So, the code above sets up an expection on the createCriteria static method of the Device domain class which says the method must be called and when it is called it returns a mock grails.orm.HibernateCriteriaBuilder object that has a 'get' method which should always return the value assigned to 'deviceCountResult'.

If you just tried:

...
get().returns(deviceCountResult)
...

then you'd find the expectation for the 'get' method would fail.

The important point is the match closure that checks to see if the argument passed is a Closure.

Hope this helps!