jMock1: Writing Custom Invocation Matchers

Invocation matchers control how a mock object matches and verifies incoming invocations against expectations and stubbed behaviour. The jMock API is a convenient syntactic sugar for specifying how matchers are added to a mock object. Sometimes the convenient API does not allow you to specify matching rules precisely or flexibly enough. In these cases you will need to write your own invocation matcher classes. This article will show you how to do just that.

A mock object contains a list of expectations (and stubs, but we can consider a stub to be a degerate form of expectation and jMock uses the same type of object to implement both). When a mock object receives an invocation it searches for an expectation to process the invocation, starting with the most recently specified expectation and working backwards until it finds match. Each expectation contains matching rules that control which invocations it matches. An expectation matches an invocation if all of its matching rules match the invocation. The jMock API provides a concise, descriptive syntax for adding expectations to a mock and adding matching rules to expectations.

An matching rule is an object that implements the InvocationMatcher interface. It has the following responsiblities:

In most cases, you will want to define a new matching rule that does change state when an invocation occurs. Also, you should always provide a description for your matching rule, so the hasDescription method should always return true. This common behaviour is already implemented in the abstract StatelessInvocationMatcher class. You can therefore define a new matching rule by extending StatelessInvocationMatcher and defining the matches and describeTo methods.

An Example Custom Matcher

Suppose we want to stub all Java Bean property getters to return some default results. Returning default results is performed by a DefaultResultStub. But how do we match property getter methods? We need to write a custom InvocationMatcher that only matches invocations of bean property getter methods. Here's what a test will look like that uses such a custom matcher:

InvocationMatcher beanPropertyGetters = new BeanPropertyGetterMatcher();
DefaultResultStub returnDefaultValue = new DefaultResultStub();
Mock mock = ...

public void testListChildren() {
    ...
    mockPerson.stubs().match(beanPropertyGetters).will(returnDefaultValue);
    ...
}

We can write the BeanPropertyGetterMatcher class by extending StatelessInvocationMatcher:

public class BeanPropertyGetterMatcher extends StatelessInvocationMatcher {
   ...
}

We now need to write the matches method to return whether the invocation is the getter method of a Java Bean property, using the Java Beans API. The method will use Bean introspection to list the property descriptors of the class that defines the invoked method. It then searches the list of property descriptors for a descriptor with a getter method that is the same as the invoked method.

public boolean matches(Invocation invocation) {
    Class beanClass = invocation.invokedMethod.getDeclaringClass();

    try {
        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
        PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();

        for( int i = 0; i < properties.length; i++ ) {
            if( invocation.invokedMethod.equals( properties[i].getReadMethod() ) ) return true;
        }
        return false;
    }
    catch( IntrospectionException ex ) {
        throw new AssertionFailedError("could not introspect bean class" + beanClass + ": " + ex.getMessage() );
    }
}

Finally we must write the describeTo method to generate a description of what our new class matches. The description will be included in the description of any expectation that uses the matcher, which itself will be included in error messages when tests fail. Our new class matches any bean property getter, so that's what our description will be:

public StringBuffer describeTo(StringBuffer buffer) {
    return buffer.append("any bean property getter");
}

The entire matcher class is shown below:

public class BeanPropertyGetterMatcher extends StatelessInvocationMatcher {

    public boolean matches(Invocation invocation) {
        Class beanClass = invocation.invokedMethod.getDeclaringClass();

        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
            PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();

            for( int i = 0; i < properties.length; i++ ) {
                if( invocation.invokedMethod.equals( properties[i].getReadMethod() ) ) return true;
            }
            return false;
        }
        catch( IntrospectionException ex ) {
            throw new AssertionFailedError("could not introspect bean class" + beanClass + ": " + ex.getMessage() );
        }
    }

    public StringBuffer describeTo(StringBuffer buffer) {
        return buffer.append("any bean property getter");
    }
}