Cleaning Up Mockito Code With Java 8

After we have finished this lesson, we

  • Know how we can configure the answer of a stubbed method by using a lambda expression.
  • Can create custom argument matchers with lambda expressions.
  • Know how we can write assertions for the actual method parameter by using a lambda expression.

Why Should We Care?

Mockito is a great tool but sometimes it forces us to write boilerplate code that shifts our attention from the essence of our test to its implementation details. In the past we had to tolerate this because we had no other choice. However, now we can fix these problems by using Java 8.

This lesson describes three tricks that help us to eliminate boilerplate code from our unit tests. Let’s start by finding out how we can configure the answer of a stubbed method by using a lambda expression.

Configuring the Answer of a Stubbed Method by Using a Lambda Expression

As we remember, if we want to configure the answer that is returned when a stubbed method is invoked, we have to use an anonymous Answer class.

For example, if we want ensure that the findById() method of the TaskRepository interface returns a non-empty Optional object when it is invoked by using the method parameter 1L, we have to write a unit test that 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.stub;
10import static org.assertj.core.api.Assertions.assertThat;
11import static org.mockito.BDDMockito.given;
12
13public class BDDMockitoJava8StubTest {
14
15    private TaskRepository repository;
16
17    @Before
18    public void createStub() {
19        repository = stub(TaskRepository.class);
20    }
21
22    @Test
23    public void shouldReturnObjectByUsingAnswerAndAnonymousClass() {
24        Task expected = dummy(Task.class);
25        given(repository.findById(1L)).willAnswer(new Answer<Optional<Task>>() {
26
27            @Override
28            public Optional<Task> answer(InvocationOnMock invocation) throws Throwable {
29                Long idParameter = (Long) invocation.getArguments()[0];
30                if (idParameter.equals(1L)) {
31                    return Optional.of(expected);
32                }
33                else {
34                    return Optional.empty();
35                }
36            }
37        });
38
39        Optional<Task> actual = repository.findById(1L);
40
41        assertThat(actual).containsSame(expected);
42    }
43}

The problem is that we have to create a new Answer object and implement the answer() method before we can configure the returned answer. This means that we have to write code that has got nothing to do with our test case just because it is required by Java programming language.

If we are using Java 8, we can configure the returned answer by using a lambda expression. After we have replaced the anonymous class with a lambda expression, the source code of our test class 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.stub;
 8import static org.assertj.core.api.Assertions.assertThat;
 9import static org.mockito.BDDMockito.given;
10
11public class BDDMockitoJava8StubTest {
12
13    private TaskRepository repository;
14
15    @Before
16    public void createStub() {
17        repository = stub(TaskRepository.class);
18    }
19
20    @Test
21    public void shouldReturnObjectByUsingAnswerAndLambdaExpression() {
22        Task expected = dummy(Task.class);
23        given(repository.findById(1L)).willAnswer(invocation -> {
24            Long idParameter = (Long) invocation.getArguments()[0];
25            if (idParameter.equals(1L)) {
26                return Optional.of(expected);
27            }
28            else {
29                return Optional.empty();
30            }
31        });
32
33        Optional<Task> actual = repository.findById(1L);
34
35        assertThat(actual).containsSame(expected);
36    }
37}

The code that uses the original Mockito API looks as follows:

1when(repository.findById(1L)).thenAnswer(invocation -> {
2	Long idParameter = (Long) invocation.getArguments()[0];
3	if (idParameter.equals(1L)) {
4		return Optional.of(expected);
5	}
6	else {
7		return Optional.empty();
8	}
9});

The benefit of this approach is that we have to write less code and the code that we have to write contains only logic that is relevant for our test case. This is a nice little trick, but we can do a lot better.

Let’s find out how we can use the unofficial Mockito add-on that helps us to stub method behavior and verify interactions by using lambda expressions.

Using the Java 8 Mockito Add-On

Getting the Required Dependencies

Before we can use the unofficial Mockito-Java8 add-on, we have to get the required dependencies. We can do this by declaring the mockito-java8 (version 0.3.1) dependency in our build script.

If we are using Maven, we can declare this dependency by adding the following snippet into our pom.xml file:

1<dependency>
2	<groupId>info.solidsoft.mockito</groupId>
3	<artifactId>mockito-java8</artifactId>
4	<version>0.3.1</version>
5	<scope>test</scope>
6</dependency>

If we are using Gradle, we can declare this dependency by adding the following snippet into our build.gradle file:

1testCompile(
2		'info.solidsoft.mockito:mockito-java8:0.3.1'
3)

Let’s find out how we can create argument matchers with lambda expressions.

Using a Lambda Matcher

If we are not using Java 8 and we want to configure the expected method parameter by using a condition that is not supported by the Matchers class, our only option is to write a custom argument matcher. Even though this is not a bad choice, we can do better. We can configure the expected method parameter by using a lambda expression.

For example, if we want to ensure that the save() method of the TaskRepository interface returns the saved Task object if the Task object that is passed as a method parameter has no id, we have to configure the expected method parameter by following these steps:

  1. Invoke the argLambda() method of the LambdaMatcher class.
  2. Specify the lambda expression and pass it as a method parameter.

After we have created our argument matcher by using a lambda expression, the source code of our unit test looks as follows:

 1import org.junit.Before;
 2import org.junit.Test;
 3
 4import static com.testwithspring.task.TestDoubles.dummy;
 5import static com.testwithspring.task.TestDoubles.stub;
 6import static info.solidsoft.mockito.java8.LambdaMatcher.argLambda;
 7import static org.assertj.core.api.Assertions.assertThat;
 8import static org.mockito.BDDMockito.given;
 9
10public class BDDMockitoJava8StubTest {
11
12    private static final Long CREATOR_ID = 1L;
13    private static final String TASK_DESCRIPTION = "description";
14    private static final String TASK_TITLE = "title";
15
16    private TaskRepository repository;
17
18    @Before
19    public void createStub() {
20        repository = stub(TaskRepository.class);
21    }
22
23    @Test
24    public void shouldReturnObjectWhenParameterIsMatchedWithLambdaMatcher() {
25        Task saved = dummy(Task.class);
26        given(repository.save(argLambda(task -> task.getId() == null)))
27				.willReturn(saved);
28
29        Task created = new TaskBuilder()
30                .withCreator(CREATOR_ID)
31                .withTitle(TASK_TITLE)
32                .withDescription(TASK_DESCRIPTION)
33                .build();
34        Task actual = repository.save(created);
35
36        assertThat(actual).isSameAs(saved);
37    }
38}

The code that uses the original Mockito API looks as follows:

1Task saved = dummy(Task.class);
2when(repository.save(argLambda(task -> task.getId() == null)))
3		.thenReturn(saved);

This technique helps us to write complex argument matchers without writing boilerplate code that we have to write when we create a “traditional” custom argument matcher.

The last trick helps us to capture the actual method parameter without writing any boilerplate code.

Using an Argument Captor - Java 8 Edition

If we are not using Java 8, and we want to capture the actual method parameter and write assertions for it, we have to create a new ArgumentCaptor object and capture the actual method parameter by using that object. Also, we have to get the captured method parameter before we can write assertions for it.

The source code of a unit test that uses an argument captor, looks as follows:

 1import org.junit.Before;
 2import org.junit.Test;
 3import org.mockito.ArgumentCaptor;
 4
 5import static org.assertj.core.api.Assertions.assertThat;
 6import static org.mockito.Mockito.mock;
 7import static org.mockito.Mockito.verify;
 8
 9public class MockitoJava8ArgumentCaptorTest {
10
11    private TaskRepository repository;
12
13    @Before
14    public void createMock() {
15        repository = mock(TaskRepository.class);
16    }
17
18    @Test
19    public void verifyThatMethodWasInvokedAndCaptureMethodParameter() {
20        repository.findById(1L);
21
22        ArgumentCaptor<Long> argument = ArgumentCaptor.forClass(Long.class);
23        verify(repository).findById(argument.capture());
24
25        Long actualId = argument.getValue();
26        assertThat(actualId).isEqualByComparingTo(1L);
27    }
28}

As we can see, our unit test has a few lines of unnecessary code. If we want to get rid off this boilerplate code, we have to capture the actual method parameter and write assertions for it by following these steps:

  1. Invoke the assertArg() method of the AssertionMatcher class.
  2. Create a lambda expression which ensures that the captured object contains the correct information.

For example, if we want to verify that the findById() method of the TaskRepository interface was invoked and 1L was passed as a method parameter, we have to write a unit test that looks as follows:

 1import org.junit.Before;
 2import org.junit.Test;
 3
 4import static info.solidsoft.mockito.java8.AssertionMatcher.assertArg;
 5import static org.assertj.core.api.Assertions.assertThat;
 6import static org.mockito.Mockito.mock;
 7import static org.mockito.Mockito.verify;
 8
 9public class MockitoJava8ArgumentCaptorTest {
10
11    private TaskRepository repository;
12
13    @Before
14    public void createMock() {
15        repository = mock(TaskRepository.class);
16    }
17
18    @Test
19    public void verifyThatMethodWasInvokedAndCaptureMethodParameter() {
20        repository.findById(1L);
21
22        verify(repository).findById(assertArg(
23                argument -> assertThat(argument).isEqualByComparingTo(1L)
24        ));
25    }
26}

As we can see, we can capture the actual method parameter and write assertions for the captured object without writing any boilerplate code. The best part of this solution is that our new unit test is a lot cleaner than the old one and it highlights the essence of our test case.

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us three things:

  • We can replace an anonymous Answer class with a lambda expression.
  • We can create custom argument matchers by using lambda expressions.
  • We can capture the actual method parameter by using a lambda expression.

Get the source code from Github