Mockito ArgumentCaptor
Credits : http://www.javabyexamples.com/mockito-recipe-capture-arguments-with-argumentcaptor
1. Overview
In this tutorial, we’ll investigate how to capture method arguments on the mocked methods using Mockito. For this purpose, we’ll use the ArgumentCaptor class. In the end, we’ll be able to capture arguments and write assertions against them.
2. Sample Application
Let’s first look at our sample application.
We’ll be using the PersonService and PersonRepository classes. Notice that PersonService includes PersonRepository as a dependency.
public class PersonRepository { ... public void delete(Person person) { System.out.println("Deleting"); } } public class PersonService { private final PersonRepository personRepository; public PersonService(PersonRepository personRepository) { this.personRepository = personRepository; } ... public void delete(Person person) { person.setName("deleted"); personRepository.delete(person); } }
SelectGithub
3. ArgumentCaptor Usage
Now, we’ll look at the usage of ArgumentCaptor which enables us to capture arguments.
Firstly, we must create an instance of ArgumentCaptor with an appropriate type parameter. Then, we must call ArgumentCaptor.capture() during the verification phase of our test:
@InjectMocks private PersonService personService; @Mock private PersonRepository personRepository; @Captor private ArgumentCaptor<Person> captor; @Test public void shouldCapture() { Person person = new Person("test"); personService.delete(person); Mockito.verify(personRepository).delete(captor.capture()); Person captured = captor.getValue(); Assertions.assertThat(captured.getName()).isEqualTo("deleted"); }
SelectGithub
Here, PersonService.delete() method sets the person’s name as “deleted”. To verify that name is indeed updated as “deleted”, we’re capturing the Person argument – on the PersonRepository.delete() method. Then we’re making our assertions.
There are some important points to note here. Firstly, we’re declaring ArgumentCaptor with Person as the type parameter – ArgumentCaptor<Person>. Secondly, we’re capturing the value on the verification phase – Mockito.verify(), not on the expectation phase – Mockito.when(). And lastly, we’re getting the captured value with the getValue() method.
4. Multiple Captures using ArgumentCaptor
Next, we’ll see how we can capture multiple values with ArgumentCaptor.
Previously, we captured only one value, since there was only one invocation. But we can also capture multiple values:
@InjectMocks private PersonService personService; @Mock private PersonRepository personRepository; @Captor private ArgumentCaptor<Person> captor; @Test public void shouldCaptureMultipleTimes() { personService.delete(new Person("test")); personService.delete(new Person("test")); Mockito.verify(personRepository, Mockito.times(2)).delete(captor.capture()); List<Person> allValues = captor.getAllValues(); for (Person captured : allValues) { Assertions.assertThat(captured.getName()).isEqualTo("deleted"); } }
SelectGithub
Here, we’re calling the delete() method twice so two values are captured. Then we’re getting the captured values with the getValues() method.
5. ArgumentCaptor Initialization
Next, let’s look at how we can initialize the ArgumentCaptor instances.
5.1. Initializing ArgumentCaptor with @Captor
Firstly, we’ll use the @Captor annotation to create a captor:
@Captor private ArgumentCaptor<Person> captor;
SelectGithub
Here, we’re declaring an ArgumentCaptor<Person> variable and annotating it with @Captor.
Next, we must make Mockito detect this annotation so that it can create an ArgumentCaptor instance. There are several ways to achieve this. Firstly, we can run the test class with Mockito’s test runner – @RunWith(MockitoJUnitRunner.class). Secondly, we can call MockitoAnnotations.initMocks(this) in the test method. Lastly, we can declare a MockitoRule instance in the test class. Note that these are the same ways to create mocks using Mockito.
In our case, we’re following the first approach and running the test with @RunWith(MockitoJUnitRunner.class).
5.2. Initializing ArgumentCaptor with ArgumentCaptor.forClass()
Another way to create an ArgumentCaptor is via the ArgumentCaptor.forClass() method. In this solution, we don’t need an annotation:
@InjectMocks private PersonService personService; @Mock private PersonRepository personRepository; @Test public void shouldCaptureManually() { ArgumentCaptor<Person> argumentCaptor = ArgumentCaptor.forClass(Person.class); Person person = new Person("test"); personService.delete(person); Mockito.verify(personRepository).delete(argumentCaptor.capture()); Person captured = argumentCaptor.getValue(); Assertions.assertThat(captured.getName()).isEqualTo("deleted"); }
SelectGithub
Here, we’re invoking ArgumentCaptor.forClass(Person.class) directly in the test method.
6. Summary
In this tutorial, we’ve looked at how we can capture method arguments using Mockito’s ArgumentCaptor class.
As always, the source code is available on Github.