Recently, I was asked to help review new tests for production code
that made use of the Builder pattern. The code in question
did not lend itself to nice and easy testing, but leveraging a
lesser-used bit of Mockito functionality helped make the
code better.
Originally, the tests created a test double for the builder object and
then stubbed all of the methods on the builder to return the builder
double itself. The code looked a bit like:
12345678910111213141516171819
@Testpublicvoidbuilder_test_v1(){Foof=mock(Foo.class);FooBuilderb=mock(FooBuilder.class);when(b.enableAlpha()).thenReturn(b);when(b.disableBeta()).thenReturn(b);when(b.increaseGamma()).thenReturn(b);when(b.build()).thenReturn(f);productionCode(b);verify(f).someMethod();}publicvoidproductionCode(FooBuilderbuilder){// code that uses the builder...Foofoo=b.enableAlpha().disableBeta().increaseGamma().build();}
There are a few downsides to this approach. The first thing we noticed
was the amount of work done to set up the builder compared to the rest
of the test. All that line noise distracts us from the meaning of the
test. This can be easily improved by pulling the builder setup into a
separate method:
While the test is now easier to read and the new method is reusable in
other tests, we still will be in trouble when the methods of the
builder change.
If your builder implements an interface, you should consider creating
an implementation of that interface that you can easily configure for
testing. Something like:
An implementation like this allows you to lean on the compiler when
the interface changes.
If you don’t have a interface to implement, you could subclass
the concrete builder class and insert your test-specific logic
there. The downside to this is that newly-added methods will inherit
their implementation from the parent class, which can cause very
strange test failures.
We did not have an interface to adhere to, so we used Mockito’s
Answer class to provide a middle ground solution. When you create a
new mock, an Answer can be used to provide default behavior for
methods. Here’s the custom Answer we came up with:
/** * Returns the mock object itself for any method that returns the specified class. */publicclassAnswerWithSelfimplementsAnswer<Object>{privatefinalAnswer<Object>delegate=newReturnsEmptyValues();privatefinalClass<?>clazz;publicAnswerWithSelf(Class<?>clazz){this.clazz=clazz;}publicObjectanswer(InvocationOnMockinvocation)throwsThrowable{Class<?>returnType=invocation.getMethod().getReturnType();if(returnType==clazz){returninvocation.getMock();}else{returndelegate.answer(invocation);}}}