Creating Dummies With Mockito

After we have finished this lesson, we

  • Know how we can create dummies with Mockito.
  • Understand how we can highlight the fact that the created test double is a dummy.

Introduction to System Under Test

The system under test (TaskService) has a single method called createCopyOf() that creates a copy of an existing task and returns a reference to created Task object. This method takes two method parameters:

  1. The id of the source task.
  2. A LoggedInUser object that contains the information of the user who is marked as the creator of the new task.

Because this lesson concentrates on creating dummies with Mockito, the whole implementation of the createCopyOf() method is not relevant for our purpose. The relevant part of the TaskService class looks as follows:

 1import com.google.common.base.Preconditions;
 2import com.testwithspring.user.LoggedInUser;
 3import org.slf4j.Logger;
 4import org.slf4j.LoggerFactory;
 5
 6public class TaskService {
 7
 8    private static final Logger LOGGER = LoggerFactory
 9			.getLogger(TaskService.class);
10
11    public TaskService() {
12    }
13
14    public Task createCopyOf(Long sourceTaskId, LoggedInUser creator) {
15        Preconditions.checkNotNull(sourceTaskId);
16        Preconditions.checkNotNull(creator);
17
18        LOGGER.info("Creating a copy of the task: {} with creator: {}",
19                sourceTaskId,
20                creator.getUsername()
21        );
22
23        //The implementation is irrelevant for this example.
24
25        return null;
26    }
27}

The LoggedInUser class contains the information of an authenticated user, and its source code looks as follows:

 1public class LoggedInUser {
 2
 3    private final Long userId;
 4    private final String username;
 5
 6    public LoggedInUser(Long userId, String username) {
 7        this.userId = userId;
 8        this.username = username;
 9    }
10
11    public Long getUserId() {
12        return userId;
13    }
14
15    public String getUsername() {
16        return username;
17    }
18}

Let’s find out how we can create dummies with Mockito.

Creating Dummies With Mockito

The first thing that we have to understand that Mockito doesn’t differentiate dummies, stubs, and mocks. The Mockito class provides one method that can be used for creating dummies, stubs, and mocks. This method is called mock(), and every test double that is created by using this method can be a mock.

That is why we must be extra careful and ensure that the purpose of our test double is crystal clear to anyone who reads our test code.

As we remember , we can highlight the fact that a test double is a dummy by giving a descriptive name to the created test double. For example, the test method that creates a LoggedInUser dummy looks as follows:

 1import org.junit.Before;
 2import org.junit.Test;
 3
 4import static org.mockito.Mockito.mock;
 5
 6public class TaskServiceTest {
 7
 8    private final Long SOURCE_TASK_ID = 99L;
 9
10    private TaskService service;
11
12    @Before
13    public void configureSystemUnderTest() {
14        service = new TaskService();
15    }
16
17    @Test
18    public void createDummyInTestMethod() {
19        LoggedInUser notImportant = mock(LoggedInUser.class);
20        service.createCopyOf(SOURCE_TASK_ID, notImportant);
21    }
22}
Technically speaking, the SOURCE_TASK_ID is also a dummy because it is irrelevant for the test case that is described here. However, I used a sane value because it will be used in the next lessons of this topic.

The question is:

What happens if the system under test invokes a method of our dummy object?

Well, because we didn’t specify the default answer when we created our dummy, our dummy returns an “empty” value such false, null, or an empty collection. Because a dummy is irrelevant for our test case, we might think that it is a good idea to ensure that our test fails if a method of our dummy is invoked.

In fact, this is a terrible idea! Let me explain.

We can configure the default answer by invoking the mock() method of the Mockito class. When we invoke this method, we have to pass two method parameters:

  1. A Class object that specifies the type of the created test double.
  2. An Answer object that configures the default answer that is returned when a method of our test double is invoked.

For example, if we want to throw a RuntimeException when a method of our dummy object is invoked, we have to create the second method parameter by following these steps:

  1. Create a new Answer object.
  2. Implement the answer() method of the Answer class by throwing a RuntimeException.

After we have configured the default answer of our dummy, 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 static org.mockito.Mockito.mock;
 7
 8public class TaskServiceTest {
 9
10    private final Long SOURCE_TASK_ID = 99L;
11
12    private TaskService service;
13
14    @Before
15    public void configureSystemUnderTest() {
16        service = new TaskService();
17    }
18
19    @Test
20    public void createDummyInTestMethod() {
21        LoggedInUser notImportant = mock(LoggedInUser.class, new Answer<LoggedInUser>() {
22            @Override
23            public LoggedInUser answer(InvocationOnMock invocation) throws Throwable {
24                throw new RuntimeException(
25						"The system under test invoked a dummy."
26				);
27            }
28        });
29        service.createCopyOf(SOURCE_TASK_ID, notImportant);
30    }
31}

The problem is that when we run our tests, we notice that this test case fails because a RuntimeException was thrown. Does this mean that the LoggedInUser object is not a dummy?

Well, no. Our test case failed because the system under test writes a log message to a log file:

1LOGGER.info("Creating a copy of the task: {} with creator: {}",
2		sourceTaskId,
3		creator.getUsername()
4);

We are not interested in this logging statement, and we definitely don’t want to test it. However, we still have to live with it, and this means that we cannot configure the default answer of a dummy if the system under test contains logging statements that want to access the property values of our dummy object.

Also, I think that configuring the default answer of a dummy doesn’t make much sense because the default answer returned by Mockito will most likely fail our tests as well and throwing an exception instead of returning a “safe” default value will make our tests more brittle.

We can also change the logging statement to take a LoggedInUser object as a method parameter. After we have done this, our logging statement looks as follows:

1LOGGER.info("Creating a copy of the task: {} with creator: {}",
2		sourceTaskId,
3		creator
4);

If we are using SLF4J and Log4j 2, our tests will pass even if we run them without disabling logging, and Log4j 2 will write the following line to our log file:

118:12:36.842 [main] INFO  com.testwithspring.task.TaskService -
2Creating a copy of the task: 99 with creator:
3[!!!com.testwithspring.user.LoggedInUser$$EnhancerByMockitoWith
4CGLIB$$cf331b6@1e802ef9=>java.lang.RuntimeException:The system
5under test invoked a dummy.!!!]

I cannot recommend this approach because of two reasons:

  1. Sometimes we want to log only one property value (such as entity id) because this makes our log files much easier to read.
  2. I have no idea if this works with other logging libraries.

We can now create dummies with Mockito and use the created dummies in our tests. However, I think that we can make this process a bit better. Let’s find out how we can do it.

Going the Extra Mile

Like I said earlier, Mockito has only one method that we can use for creating dummies, stubs, and mocks. The problem is that the name of this method is mock() which suggests that the created object is a mock as well.

This might seem like a minor issue, but I think that if we want to highlight the type of the created test double, we must fix this problem.

Luckily, this is an easy problem to solve. We can solve it by following these steps:

First, we have to create a public and final class called TestDoubles and add a private constructor into the created class. This ensures that developers cannot extend or instantiate our object mother class.

The source code of the TestDoubles class looks as follows:

1public final class TestDoubles {
2
3    private TestDoubles() {}
4}

Second, we have to add a public and static factory method called dummy() into our object mother class. This method takes a Class<T> object as a method parameter, passes its method parameter forward to the mock() method of the Mockito class, and returns the created test double.

After we have added this method into the TestDoubles class, its source code looks as follows:

 1import static org.mockito.Mockito.mock;
 2
 3public final class TestDoubles {
 4
 5    private TestDoubles() {}
 6
 7    public static <T> T dummy(Class<T> dummyClass) {
 8        return mock(dummyClass);
 9    }
10}

Third, we have to modify our test method to create a dummy object by using our new object mother class. After have done this, the source code of our test class looks as follows:

 1import org.junit.Before;
 2import org.junit.Test;
 3
 4import static com.testwithspring.task.TestDoubles.dummy;
 5
 6public class TaskServiceTest {
 7
 8    private final Long SOURCE_TASK_ID = 99L;
 9
10    private TaskService service;
11
12    @Before
13    public void configureSystemUnderTest() {
14        service = new TaskService();
15    }
16
17    @Test
18    public void createDummyInTestMethod() {
19        LoggedInUser notImportant = dummy(LoggedInUser.class);
20        service.createCopyOf(SOURCE_TASK_ID, notImportant);
21    }
22}

We can now use two different methods for highlighting the fact that the created test double is a dummy:

  • We can use the name of the variable (or constant) that contains a reference to our dummy.
  • We can create our dummy by invoking a method that is called dummy().

The reason why I think that we should do our best to avoid misunderstandings is that quite often people who are using Mockito are calling all test doubles mocks. In fact, I have done this too in the past.

This is dangerous because all test doubles created with the mock() method are capable of being mocks, and that is why I think that adding an extra object mother class is definitely worth it. Our new object mother class helps us to clarify our intentions and reduce misunderstandings.

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us three things:

  • Mockito doesn’t differentiate dummies, stubs, and mocks.
  • We shouldn’t configure the default answer of a dummy.
  • We shouldn’t create our dummies by invoking the mock() method in our test class.

Get the source code from Github