Custom Stubs in jMock
or Unusual Behaviour Deserves to be Mocked
A mock object does two things: it tests that it receives the expected method invocations and it stubs the behaviour of those methods. Almost all methods can be stubbed in one of three basic ways: return nothing (a void method), return a result to the caller or throw an exception. Sometimes, however, you need to stub side effects of a method, such as a method that calls back to the caller through an object reference passed as one of its parameters. Luckily, jMock makes it easy to write custom stubs for methods with strange behaviours and keep your tests easy to read when you do so.
Here's a simple example: we want to test a NameCollector
object that collects the names of a collection Named
objects. It
will do so by passing a List
to the collectName
method of the named objects. The named objects implement
collectName
by addding their name to the list they receive. The
Named
interface is shown below:
public interface Named { void collectName( List list ); }
To test the behaviour of our object we will need to mock the
Named
interface, and in particular we need to stub the side
effect of the collectName
method. jMock provides stubs for
returning values and throwing exceptions but we will have to write our own
class to stub the side effect of the Named
objects.
To write a stub:
Write a class that implements the
Stub1
interface. Here's a stub that adds an element to a List received as the first argument of the method.public class AddToListStub implements Stub { Object element; public AddToListStub( Object element ) { this.element = element; } public StringBuffer describeTo( StringBuffer buffer ) { return buffer.append("adds <"+element+"> to a list"); } public Object invoke( Invocation invocation ) throws Throwable { ((List)invocation.parameterValues.get(0)).add(element); return null; } }
Write a factory method in your tests class that instantiates your new stub class.
private Stub addListElement( Object newElement ) { return new AddToListStub(newElement); }
Call your factory method within the actual parameter list of the mock's
will
method.Mock mockNamed = mock(Named.class); String name = "name"; mockNamed.expects(once()).method("collectName").with(NOT_NULL) .will(addListElement(name));