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);
    }
}

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");
}

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");
    }
}

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;

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");
}

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.