Sunday, February 7, 2010

New in Groovy 1.7.1: Constructor Mocking and Half Mocks

The Groovy MockFor object got some fun new features this weekend: constructor mocking and "half-mocks". The tickets are marked for Groovy 1.7.1, so these features are available in the nightly builds or in the next few weeks as 1.7.1 is released. The easiest thing is, of course, to just build from source.

The first feature is Constructor mocking. It is now possible to specify a return value for mock constructor calls. Here is an example that uses a "Person" object. The "tom" variable is a real Person object, while the "mary" object is not. Thanks to this new feature, instantiating Mary returns to you Tom.

import groovy.mock.interceptor.MockFor

class Person {
String name
}

def tom = new Person(name:'Tom')

def mock = new MockFor(Person, true)

mock.demand.with {
Person() { tom }
}

mock.use {
def mary = new Person(name:'Mary')
assert tom == mary
}
There are two parts to getting this working: the "demand.with" block, where the person constructor is specified to return tom, and the true parameter passed to MockFor. To make MockFor backwards compatible, a flag was needed to control when to allow constructor mocking and when not to allow it. As you can see, after doing this, the tom and mary objects are equal (in fact, the same reference).

The problem with this feature alone is that calling tom.getName() results in a mock exception saying no demand is set. If Tom is a real instance of Person then why would you get a demand exception? Well, within the mock.use block Tom is a mock. The behaviour you probably expect is to have the real Tom methods pass through to the real implementation, which is why "half-mocks" were introduced. Check out how you can specify methods to ignore and pass through to the underlying object:
mock.ignore(~'get.*')
mock.demand.with {
Person() { tom }
}

mock.use {
def mary = new Person(name:'Mary')
assert mary.name == 'Tom'
}
The ignore method takes a pattern to match, and the mock ignores those methods, passing them on to the underlying implementation. So your mock is still a mock, not a real object, but methods are passed through as needed. This is the opposite of how EasyMock partial mocking works, where a partial mock is technically a subclass of the target class, and methods are routed by default to the real implementation. In Groovy, the method are routed by default to the mock.

Fun stuff, and thanks to Paul King for the work!

No comments: