Szczepan was kind enough to spent some time with me tonight chatting
about his puzzles on whether to include a
feature into Mockito that allows a stubbed method to return differently
on different calls.
Clearly jmock and easymock allow you to chain return values and even pass in callbacks,
that the mock evaluates to answer the call. But we had a feeling that this might be bad
and that you should roll your own stub in such cases. So we tried an example of class that seems
reasonable well factored and hard to test without such chaining. Then we implemented the
tests using our own purpose-built stub and the hypothetical Mockito syntax.
We came up with a Reader
interface, that is like an Iterator, but returns an endless sequence of values.
This might seem to be a bit contrived, but for monitoring and control sysemtems the notion of an infinite stream
of sensor data doesn’t seem to be wrong.
public interface Reader<T> { public T read(); }
The unit under test is a class that wraps a Reader
and implements the Reader
interface itself.
I called it a CompressingReader
. It is actually delegating to the wrapped Reader
, but returns
only changed valus. So sequences of unchanged values are “compressed” to one value. So if a sensor implementing
Reader
returns {2, 2, 4, 4, 4, 5, 4} the CompressingReader
will return {2, 4, 5, 4}.
Being a good test-driven developer you are certainly first going to write a test, but how?
So first the solution I would have gone for before I worked on projects that used mockin libraries. I wrote a small stub
that implements the Reader
interface and returns values based on a String
that has been passed
in. This is effectivly a minimalistic interpreter.
class ReaderStub implements Reader<String> { private final String instructions; private int numberOfCalls; public ReaderStub(String instructions) { this.instructions = instructions; } public String read() { return Character.toString(instructions.charAt(numberOfCalls++)); } }
So with that the actual tests can be implemented along the lines of the following two:
public void testReturnOneValueFoTwoEqualConsecutiveValues(){ ReaderStub reader = new ReaderStub("aac"); CompressingReader<String> compressingReader = new CompressingReader<String>(reader); assertEquals("a", compressingReader.read()); assertEquals("c", compressingReader.read()); } public void testReturnSameValueAgainIfNotConsecutive(){ ReaderStub reader = new ReaderStub("aca"); CompressingReader<String> compressingReader = new CompressingReader<String>(reader); assertEquals("a", compressingReader.read()); assertEquals("c", compressingReader.read()); assertEquals("a", compressingReader.read()); }
To me this didn’t seem to be too bad, but then have a look at what Mockito gives us.
public void testReturnOneValueFoTwoEqualConsecutiveValues(){ Reader<String> reader = mock(Reader.class); stub(reader.read()) .toReturn("a") .toReturn("a") .toReturn("c"); CompressingReader<String> compressingReader = new CompressingReader<String>(reader); assertEquals("a", compressingReader.read()); assertEquals("c", compressingReader.read()); } public void testReturnSameValueAgainIfNotConsecutive(){ Reader<String> reader = mock(Reader.class); stub(reader.read()) .toReturn("a") .toReturn("c") .toReturn("a"); CompressingReader<String> compressingReader = new CompressingReader<String>(reader); assertEquals("a", compressingReader.read()); assertEquals("c", compressingReader.read()); assertEquals("a", compressingReader.read()); }
For these to testcases it seems to be fairly equal – 20 lines for the handcrafted test, 21 for the Mockito test. But
the readability of the Mockito test ist very good. And the feature is not messing up the test. For more complicated
examples, where we might also be interested in throwing an Exception for some of the invocations, this solution might
even be simpler. Apart from that the Syntax could be improved to return values from an array, varargs or collection.
Then we could rewrite the above tests to:
public void testReturnOneValueFoTwoEqualConsecutiveValues(){ Reader<String> reader = mock(Reader.class); stub(reader.read()) .toReturnSequentially("a","a","c"); CompressingReader<String> compressingReader = new CompressingReader<String>(reader); assertEquals("a", compressingReader.read()); assertEquals("c", compressingReader.read()); } public void testReturnSameValueAgainIfNotConsecutive(){ Reader<String> reader = mock(Reader.class); stub(reader.read()) .toReturnSequentially("a","b","a"); CompressingReader<String> compressingReader = new CompressingReader<String>(reader); assertEquals("a", compressingReader.read()); assertEquals("c", compressingReader.read()); assertEquals("a", compressingReader.read()); }
This solution is actually down to 17 lines of code and a more appropriate comparison. It shows, that using Mockito for
such stubs is not only “not harmful” but actually beneficial!
Leave a Reply