Partial mocks are an advanced feature of modern mocking libraries like mockito. Partial mocks retain the original code of a class only stubbing the methods you specify. If you build your system largely from scratch you most likely will not need to use them. Sometimes there is no easy way around them when working with dependencies not designed for testability. Let us look at an example:
/** * Evil dependency we cannot change */ public final class CarvedInStone { public CarvedInStone() { // may do unwanted things } public int thisHasSideEffects(int i) { return 31337; } // many more methods } public class ClassUnderTest { public Result computeSomethingInteresting() { // some interesting stuff int intermediateResult = new CarvedInStone().thisHasSideEffects(42); // more interesting code return new Result(intermediateResult * 1337); } }
We want to test the computeSomethingInteresting()
method of our ClassUnderTest
. Unfortunately we cannot replace CarvedInStone
, because it is final and does not implement an interface containing the methods of interest. With a small refactoring and partial mocks we can still test almost the complete class:
public class ClassUnderTest { public int computeSomethingInteresting() { // some interesting stuff int intermediateResult = intermediateResultsFromCarvedInStone(42); // more interesting code return intermediateResult * 1337; } protected int intermediateResultsFromCarvedInStone(int input) { return new CarvedInStone().thisHasSideEffects(input); } }
We refactored our dependency into a protected method we can use to stub out with our partial mocking to be tested like this:
public class ClassUnderTestTest { @Test public void interestingComputation() throws Exception { ClassUnderTest cut = spy(new ClassUnderTest()); doReturn(1234).when(cut).intermediateResultsFromCarvedInStone(42); assertEquals(1649858, cut.computeSomethingInteresting()); } }
Caveat: Do not use the usual when-thenReturn-style:
when(cut.intermediateResultsFromCarvedInStone(42)).thenReturn(1234);
with partial mocks because the real method will get called once!
So the only untested code is a simple delegation. Measures like that refactoring and partial mocking generally serve as a first step and not the destination.
Where to go from here
To go the whole way we would encapsulate all unmockable dependencies into wrapper objects providing the functionality we need here and inject them into our ClassUnderTest. Then we can replace our wrapper(s) easily using regular mocking.
Doing all this can be a lot of work and/or risk depending on the situation so the depicted process serves as an low risk intermediate step for getting as much important code under test as possible.
Note that the wrappers themselves stay largely untestable like our protected delegating method.