Creating Strict Stubs With Mockito
After we have finished this lesson, we
- Know what a strict stub is.
- Know how we can create strict stubs.
- Can configure a strict stub to return an object or throw an exception when its method is invoked.
- Understand when we should use strict stubs.
This lesson assumes that:
Creating a Strict Stub
Before we can create a strict stub, we have to know what a strict stub is. Here is my definition for the term strict stub:
A strict stub is a stub that fails the test if the system under test invokes an unexpected method.
In other words, if the system under test invokes a method whose behavior has not been configured, a strict stub must fail the test.
We can create a strict stub by overriding the Mockito’s default answer that returns an “empty” value if the default answer of the created test double was not specified when it was created. We can specify the default answer by following these steps:
First, we have to add a public and static method called stricStub() into the TestDoubles class. This method takes a Class<T> object that specifies the type of the created stub as a method parameter and returns the created stub.
After we have added this method into the TestDoubles class, its source code looks as follows:
1import org.mockito.invocation.InvocationOnMock;
2import org.mockito.stubbing.Answer;
3
4import static org.mockito.Mockito.mock;
5
6public final class TestDoubles {
7
8 private TestDoubles() {}
9
10 public static <T> T dummy(Class<T> dummyClass) {
11 return mock(dummyClass);
12 }
13
14 public static <T> T strictStub(Class<T> stubClass) {
15
16 }
17
18 public static <T> T stub(Class<T> stubClass) {
19 return mock(stubClass);
20 }
21}
Second, we have to implement the strictStub() method by invoking the mock() method of the Mockito class and returning the created test double. When we invoke this method, we have to pass two method parameters to it:
- The
Class<T>object that specifies the type of the created stub. - An
Answerobject that throws aRuntimeExceptionwhen itsanswer()method is invoked.
After we have implemented the strictStub() method, the source code of the TestDoubles class looks as follows:
1import org.mockito.invocation.InvocationOnMock;
2import org.mockito.stubbing.Answer;
3
4import static org.mockito.Mockito.mock;
5
6public final class TestDoubles {
7
8 private TestDoubles() {}
9
10 public static <T> T dummy(Class<T> dummyClass) {
11 return mock(dummyClass);
12 }
13
14 public static <T> T strictStub(Class<T> stubClass) {
15 return mock(stubClass, new Answer() {
16 @Override
17 public Object answer(InvocationOnMock invocation) throws Throwable {
18 throw new RuntimeException(
19 "SUT invoked an unexpected method of a stub"
20 );
21 }
22 });
23 }
24
25 public static <T> T stub(Class<T> stubClass) {
26 return mock(stubClass);
27 }
28}
We can now create strict stubs by using our new factory method. Let’s find out we can stub method behavior with Mockito when we are using a strict stub.
Stubbing Method Behavior With Mockito
The first thing that we have to do is to create our strict stub. Because the test doubles created with Mockito are not stateless, we have to create our stub before a test method is invoked. We can do this by creating our stub in a setup method that is annotated with the @Before annotation.
After we have added this setup method into our test, its source code looks as follows:
1import org.junit.Before;
2
3import static com.testwithspring.task.TestDoubles.strictStub;
4
5public class BDDMockitoTest {
6
7 private TaskRepository repository;
8
9 @Before
10 public void createStrictStub() {
11 repository = strictStub(TaskRepository.class);
12 }
13}
When we want to stub a method of a “normal” stub, we have to follow these steps:
- Specify the stubbed method call.
- Specify the answer of the stubbed method.
If we want to stub a method a strict stub, we have to execute these steps in “reverse” order. In other words, we have to follow these steps:
- Specify the answer of the stubbed method.
- Specify the stub whose behavior we want to configure.
- Specify the stubbed method call.
Let’s take a look at some examples that demonstrate how we can stub the methods of a strict stub.
Returning Values and Objects
If we want to configure the returned value or object by using BDDMockito, we have to follow these steps:
- Specify the returned value by invoking the
willReturn()method of theBDDMockitoclass. When we invoke this method, we have to pass the returned value or object as a method parameter. - Specify the stub whose behavior we want to configure by invoking the
given()method of theBDDStubberinterface. When we invoke this method, we have to pass our stub object as a method parameter. - Specify the stubbed method call by invoking the stubbed method. When we invoke the stubbed method, we have to specify the expected method parameters.
After we have configured the findById() method of the TaskRepository interface to return an non-empty Optional object when it is invoked by using the method parameter 1L, the source code of our test method looks as follows:
1import org.junit.Before;
2import org.junit.Test;
3
4import java.util.Optional;
5
6import static com.testwithspring.task.TestDoubles.dummy;
7import static com.testwithspring.task.TestDoubles.strictStub;
8import static org.assertj.core.api.Assertions.assertThat;
9import static org.mockito.BDDMockito.willReturn;
10
11public class BDDMockitoTest {
12
13 private TaskRepository repository;
14
15 @Before
16 public void createStrictStub() {
17 repository = strictStub(TaskRepository.class);
18 }
19
20 @Test
21 public void shouldReturnObject() {
22 Task expected = dummy(Task.class);
23 willReturn(Optional.of(expected)).given(repository).findById(1L);
24
25 Optional<Task> actual = repository.findById(1L);
26
27 assertThat(actual).containsSame(expected);
28 }
29}
If we want to use the original Mockito API, we have to follow the same steps but invoke different methods. We can configure the returned value by invoking the doReturn() method of the Mockito class and specify the stub whose behavior we want to configure by invoking the when() method of the Stubber interface.
The code that uses the original Mockito API, looks as follows:
1doReturn(Optional.of(expected)).when(repository).findById(1L);
Throwing an Exception
If we want to configure the exception that is thrown when a method of a stub is invoked, we have to follow these steps:
- Specify the thrown exception by invoking the
willThrow()method of theBDDMockitoclass. When we invoke this method, we have to pass the thrown exception as a method parameter. - Specify the stub whose behavior we want to configure by invoking the
given()method of theBDDStubberinterface. When we invoke this method, we have to pass our stub object as a method parameter. - Specify the stubbed method call by invoking the stubbed method. When we invoke the stubbed method, we have to specify the expected method parameters.
After we have configured the findById() method of the TaskRepository interface to throw a NotFoundException when it is invoked by using the method parameter 1L, the source code of our test method looks as follows:
1import org.junit.Before;
2import org.junit.Test;
3
4import java.util.Optional;
5
6import static com.testwithspring.task.TestDoubles.dummy;
7import static com.testwithspring.task.TestDoubles.strictStub;
8import static org.assertj.core.api.Assertions.assertThat;
9import static org.mockito.BDDMockito.willThrow;
10
11public class BDDMockitoTest {
12
13 private TaskRepository repository;
14
15 @Before
16 public void createStrictStub() {
17 repository = strictStub(TaskRepository.class);
18 }
19
20 @Test(expected = NotFoundException.class)
21 public void shouldThrowExceptionWhenMethodHasReturnValue() {
22 willThrow(new NotFoundException()).given(repository).findById(1L);
23
24 repository.findById(1L);
25 }
26}
This method cannot throw an exception. If the requested task is not found, this method returns an empty Optional.
I use this example here because I wanted to demonstrate how we can throw an exception if the stubbed method returns something. That being said, we must not break the API contract of our API when we stub method behavior!
If we want to use the original Mockito API, we have to follow the same steps but invoke different methods. We can configure the thrown exception by invoking the doThrow() method of the Mockito class and specify the stub whose behavior we want to configure by invoking the when() method of the Stubber interface.
The code that uses the original Mockito API, looks as follows:
1doThrow(new NotFoundException()).when(repository).findById(1L);
Configuring an Answer
If we want create an Answer<Optional<Task>> object which ensures that the findById() method of the TaskRepository interface returns a non-empty Optional object when it is invoked by using a method parameter that is equal to 1L, we have to follow these steps:
- Specify the returned answer by invoking the
willAnswer()method of theBDDMockitoclass. When we invoke this method, we create a newAnswer<Optional<Task>>object which returns a non-emptyOptionalobject if the actual method parameter is equal to1Land pass the created object as a method parameter. - Specify the stub whose behavior we want to configure by invoking the
given()method of theBDDStubberinterface. When we invoke this method, we have to pass our stub object as a method parameter. - Specify the stubbed method call by invoking the stubbed method. When we invoke the stubbed method, we have to specify the expected method parameters.
After we configured the behavior of the findById() method by using an Answer<Optional<Task>> object, the source code of our test method looks as follows:
1import org.junit.Before;
2import org.junit.Test;
3import org.mockito.invocation.InvocationOnMock;
4import org.mockito.stubbing.Answer;
5
6import java.util.Optional;
7
8import static com.testwithspring.task.TestDoubles.dummy;
9import static com.testwithspring.task.TestDoubles.strictStub;
10import static org.assertj.core.api.Assertions.assertThat;
11import static org.mockito.BDDMockito.willAnswer;
12
13public class BDDMockitoTest {
14
15 private TaskRepository repository;
16
17 @Before
18 public void createStrictStub() {
19 repository = strictStub(TaskRepository.class);
20 }
21
22 @Test
23 public void shouldReturnObjectByUsingAnswer() {
24 Task expected = dummy(Task.class);
25 willAnswer(new Answer<Optional<Task>>() {
26 @Override
27 public Optional<Task> answer(InvocationOnMock invocation) throws Throwable {
28 Long idParameter = (Long) invocation.getArguments()[0];
29 if (idParameter.equals(1L)) {
30 return Optional.of(expected);
31 }
32 else {
33 return Optional.empty();
34 }
35 }
36 }).given(repository).findById(1L);
37
38 Optional<Task> actual = repository.findById(1L);
39
40 assertThat(actual).containsSame(expected);
41 }
42}
We can now configure the behavior of a strict stub by using Mockito. Let’s move on and find out when we should use strict stubs.
If we want to use the original Mockito API, we have to follow the same steps but invoke different methods. We can configure the answer by invoking the doAnswer() method of the Mockito class and specify the stub whose behavior we want to configure by invoking the when() method of the Stubber interface.
The code that uses the original Mockito API, looks as follows:
1doAnswer(new Answer<Optional<Task>>() {
2 @Override
3 public Optional<Task> answer(InvocationOnMock invocation)
4 throws Throwable {
5 Long idParameter = (Long) invocation.getArguments()[0];
6 if (idParameter.equals(1L)) {
7 return Optional.of(expected);
8 }
9 else {
10 return Optional.empty();
11 }
12 }
13}).when(repository).findById(1L);
The example application of this lesson provides more examples that demonstrate how we can configure the behavior of a strict stub.
Should We Use Strict Stubs?
Strict stubs have one major benefit: strict stubs guarantee that our test fails if the system under test invokes a method whose behavior has not been configured. Even though the default answer returned by Mockito will most likely fail our test, we cannot be sure that our test will fail if the system under test invokes an unexpected method of our stub.
This sounds like a huge benefit. So, should we use strict stubs?
Yes. And no.
In theory, using strict stubs is an excellent idea because they increase our trust in our test suite. However, they also force us to use a syntax that doesn’t feel natural. We can see the difference by comparing these two sentences:
- When X is invoked by using the input Y, return Z
- Return Z when X is invoked by using the input Y
The first sentence describes the API that we use for configuring a normal stub. The second sentence describes the API that we use when we configure a strict stub.
I don’t want to make a recommendation because I know that people have different preferences. I think that you have two options:
- If you think that the second sentence is more natural than the first one OR you value the security provided by strict stubs over readability, you should use strict stubs.
- If you think that the first sentence is more natural than the second one AND you value readability over the security provided by strict stubs, you should not use strict stubs.
Let’s summarize what we learned from this lesson:
Summary
This lesson has taught us four things:
- We can create a strict stub by replacing the Mockito’s default answer with an answer that throws an exception.
- If we want to stub a method of a strict stub, we have to execute the required steps in “reverse” order.
- Strict stubs guarantee that our test case fails if the system under test invokes a method whose behavior has not been configured.
- Strict stubs force us to use a syntax that might not feel natural.