Benji's Blog

Test Stubbing with Builders, Real Objects, and Caches

I thought I’d start posting some of my notes on tips for testing. Starting with some tips and tricks for Mockito.

Mocking/Stubbing frameworks like Mockito help to test units of code in isolation with minimal boilerplate.

A couple of guidelines I like to aim to follow when writing tests are:

Sometimes it can be hard to write consise tests for consise, readable code. It’s often tempting to compromise the simplicity of the code under test in order to make the tests easier. However, Mockito is flexible enough that this can usually be avoided.

Obviously we have to be careful. Often (perhaps even usually) code being hard to test is a smell, and it’s better to re-think how the code is written to make it more naturally testable.

Here are three things that can make tests more difficult:

Use of the Builder Pattern

Here’s the first example. We’d like to write some code like the following.

public class Example1 {
	FoxBuilder foxBuilder;
	Dog dog;

	public void someMethod() {
		Fox fox = foxBuilder
			.speed(QUICK)
			.colour(BROWN)
			.legs(4)
			.longTail()
			.gender(MALE)
			.build();

		fox.jumpOver(dog);
	}
}

If you have a basic familiarity with Mockito you might be tempted to write a test like the following. Unfortunately here

@RunWith(MockitoJUnitRunner.class)
public class Example1NaiveTest {
	@Mock FoxBuilder foxBuilder;
	@Mock Fox fox;
	@Mock Dog dog;

	@InjectMocks
	Example1 example = new Example1();

	@Test public void naiveTest() {
		when(foxBuilder.speed(QUICK)).thenReturn(foxBuilder);	
		when(foxBuilder.colour(BROWN)).thenReturn(foxBuilder);
		when(foxBuilder.legs(4)).thenReturn(foxBuilder);
		when(foxBuilder.longTail()).thenReturn(foxBuilder);
		when(foxBuilder.gender(MALE)).thenReturn(foxBuilder);
		when(foxBuilder.build()).thenReturn(fox);

		example.someMethod();

		verify(fox).jumpOver(dog);
	}
}

Omitting one of the when() stubbings from the above test will result in a NullPointerException.

Fortunately Mockito has a solution. When a method invocation on a mock has not been stubbed in the test, Mockito will fall back to the “default answer”. We can also specify what the default answer will be. So let’s create a default answer suitable for builders.

Here we create an Answer to make Mocks return themselves from any method invocation on them that has a compatible return type.

public class Return {
	public static Answer> Self = new Answer() {
		public Object answer(InvocationOnMock invocation) throws Throwable {
			if (invocation.getMethod().getReturnType().isAssignableFrom(invocation.getMock().getClass())) {
				return invocation.getMock();
			}
			
			return null;
		}
	};
}

Now our test can look like this. We only have to stub out the build invocation. Notice the instantiations of the Mocks now tell Mockito to use our new default Answer.

@RunWith(MockitoJUnitRunner.class)
public class Example1Test {
	FoxBuilder foxBuilder = mock(FoxBuilder.class, Return.Self);
	FoxBuilder quickFoxBuilder = mock(FoxBuilder.class, Return.Self);

	@Mock Fox fox;
	@Mock Dog dog;

	@InjectMocks
	Example1 example = new Example1();

	@Test public void whenSomeMethodCalled_aFox_shouldJumpOverTheLazyDog() {
		when(foxBuilder.build()).thenReturn(fox);
		example.someMethod();
		verify(fox).jumpOver(dog);
	}
}

“Ah”, you might say. “Now we’re no longer checking we build a fox of the right type”. Well, if that’s important to us we can put it in another test. That way we stick to one test per item of behaviour we want to assert.

We can assert that the speed method on the builder is called

@Test public void whenSomeMethodCalled_shouldCreateQuickFox() {
	when(foxBuilder.build()).thenReturn(fox);
	example.someMethod();
	verify(foxBuilder).speed(QUICK);
}

Or, to more properly check that the dog is jumped over by a fox-that-is-quick we could utilise two builders to represent a state transition:

@Test public void whenSomeMethodCalled_shouldJumpOverAFoxThatIsQuick() {
	when(foxBuilder.speed(QUICK)).thenReturn(quickFoxBuilder);
	when(quickFoxBuilder.build()).thenReturn(fox);
	example.someMethod();
	verify(fox).jumpOver(dog);
}

 

Real Objects

Now, suppose we decided that the dog should be instantiated within the method instead of a field on Example. Making it hard to test.

Here is the code we want to write

public class Example2 {
	FoxBuilder foxBuilder;

	public void someMethod() {
		Fox fox = foxBuilder
			.speed(QUICK)
			.colour(BROWN)
			.legs(4)
			.longTail()
			.gender(MALE)
			.build();

		Dog dog = new Dog();

		dog.setLazy();

		fox.jumpOver(dog);
	}
}

We could create a dogFactory and stub out the creation of the dog. However, this adds complexity and changes the implementation for the test.
We could use powermock to mock the Dog’s constructor.
However, there can be valid reasons not to mock objects like this. For example it’s good to avoid mocking Value Objects.

So, how can we test it with a real object? Use a Mockito utility called an ArgumentCaptor

Here we capture the real Dog object passed to the Fox mock, and can perform assertions on it afterwards.

@Test public void whenSomeMethodCalled_aRealFox_shouldJumpOverTheLazyDog() {
	when(foxBuilder.build()).thenReturn(fox);
	example.someMethod();

	ArgumentCaptor dogCaptor = ArgumentCaptor.forClass(Dog.class);
	verify(fox).jumpOver(dogCaptor.capture());
	assertTrue(dogCaptor.getValue().isLazy());		
}

Methods that Return Arguments

 

Now let’s make it harder again. Suppose the real dog passes through another object such as a cache that we’d like to stub.

public class Example3 {
	FoxBuilder foxBuilder;
	Cache cache;

	public void someMethod() {
		Fox fox = foxBuilder
			.speed(QUICK)
			.colour(BROWN)
			.legs(4)
			.longTail()
			.gender(MALE)
			.build();

		Dog dog = new Dog();
		dog.setLazy();

		dog = cache.put(dog);

		fox.jumpOver(dog);
	}
}

These presents some challenges to test if we have stubbed the cache. One approach would be to test both the dog being lazy and the cache-addition in the same test, re-using the ArgumentCaptor used above.

This is undesirable because there are two things being asserted in a single test.

@RunWith(MockitoJUnitRunner.class)
public class Example3Test {
	FoxBuilder foxBuilder = mock(FoxBuilder.class, Return.Self);
	FoxBuilder quickFoxBuilder = mock(FoxBuilder.class, Return.Self);

	@Mock
	Cache mockCache;
	@Mock
	Fox fox;
	@Mock 
	Dog dog;

	@InjectMocks
	Example3 example = new Example3();

	@Test public void bad_whenSomeMethodCalled_aRealFox_shouldJumpOverTheLazyDog() {
		when(foxBuilder.build()).thenReturn(fox);
		when(mockCache.put(any(Dog.class))).thenReturn(dog);

		example.someMethod();

		ArgumentCaptor dogCaptor = ArgumentCaptor.forClass(Dog.class);
		verify(mockCache).put(dogCaptor.capture());
		assertTrue(dogCaptor.getValue().isLazy());

		verify(fox).jumpOver(dog);
	}
}

The trick is to use a Mockito Answer again to create a custom stubbing rule. Here we define an Answer that will return an argument passed to the mock method invocation.

public static  Answer argument(final int num) {
	return new Answer() {
		@SuppressWarnings("unchecked")
		public T answer(InvocationOnMock invocation) throws Throwable {
			return (T) invocation.getArguments()[num - 1];
		}
	};
}

Using this, the test is only one line longer than it was before adding the cache. We could even move the cache stubbing (2nd line of test) to an @Before section to further declutter the test (as it’s generic cache behaviour)

@Test public void whenSomeMethodCalled_aRealFox_shouldJumpOverTheLazyDog() {
	when(foxBuilder.build()).thenReturn(fox);
	when(mockCache.put(any())).thenAnswer(argument(1));

	example.someMethod();
	ArgumentCaptor dogCaptor = ArgumentCaptor.forClass(Dog.class);

	verify(fox).jumpOver(dogCaptor.capture());
	assertTrue(dogCaptor.getValue().isLazy());
}

If we actually want to assert the caching happens we can write another test. This test is consise and if it fails we will know why.

If we cared about the specific properties of the dog being cached we could add the ArgumentCaptor back in.

@Test public void whenSomeMethodCalled_aFoxShouldBeCached() {
	when(foxBuilder.build()).thenReturn(fox);
	example.someMethod();
	verify(mockCache).put(any(Dog.class));
}

The examples used in this post are on github

There are lots of other things you can use Mockito Answers for. Take a look at the Answers enum for some of the default answers provided. RETURN_DEEP_STUBS can be useful, particularly for testing legacy code.