Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll cover a common use case of using Mockito ArgumentCaptor in our unit tests.

Alternatively, for other Mockito.verify use cases, see our Mockito Verify Cookbook.

Further reading:

Introduction to Mockito's AdditionalAnswers

A quick and practical guide to Mockito's AdditionalAnswers.

Mockito ArgumentMatchers

Learn how to use the ArgumentMatcher and how it differs from the ArgumentCaptor.

Mockito Verify Cookbook

<strong>Mockito Verify</strong> examples, usage and best practices.

2. Using ArgumentCaptor

ArgumentCaptor allows us to capture an argument passed to a method to inspect it. This is especially useful when we can’t access the argument outside of the method we’d like to test.

For example, consider an EmailService class with a send method that we’d like to test:

public class EmailService {

    private DeliveryPlatform platform;

    public EmailService(DeliveryPlatform platform) {
        this.platform = platform;
    }

    public void send(String to, String subject, String body, boolean html) {
        Format format = Format.TEXT_ONLY;
        if (html) {
            format = Format.HTML;
        }
        Email email = new Email(to, subject, body);
        email.setFormat(format);
        platform.deliver(email);
    }

    ...
}

In EmailService.send, notice how platform.deliver takes a new Email as an argument. As part of our test, we’d like to check that the format field of the new Email is set to Format.HTML. To do this, we need to capture and inspect the argument that is passed to platform.deliver.

Let’s see how we can use ArgumentCaptor to help us.

2.1. Set Up the Unit Test

First, let’s create our unit test class:

@ExtendWith(MockitoExtension.class)
class EmailServiceUnitTest {

    @Mock
    DeliveryPlatform platform;

    @InjectMocks
    EmailService emailService;
  
    ...
}

We’re using the @Mock annotation to mock DeliveryPlatform, which is automatically injected into our EmailService with the @InjectMocks annotation. Refer to our Mockito annotations article for further details.

2.2. Add an ArgumentCaptor Field

Second, let’s add a new ArgumentCaptor field of type Email to store our captured argument:

@Captor
ArgumentCaptor<Email> emailCaptor;

2.3. Capture the Argument

Third, let’s use verify() with the ArgumentCaptor to capture the Email:

verify(platform).deliver(emailCaptor.capture());

We can then get the captured value and store it as a new Email object:

Email emailCaptorValue = emailCaptor.getValue();

2.4. Inspect the Captured Value

Finally, let’s see the whole test with an assert to inspect the captured Email object:

@Test
void whenDoesSupportHtml_expectHTMLEmailFormat() {
    String to = "[email protected]";
    String subject = "Using ArgumentCaptor";
    String body = "Hey, let'use ArgumentCaptor";

    emailService.send(to, subject, body, true);

    verify(platform).deliver(emailCaptor.capture());
    Email value = emailCaptor.getValue();
    assertThat(value.getFormat()).isEqualTo(Format.HTML);
}

3. Avoiding Stubbing

Although we can use an ArgumentCaptor with stubbing, we should generally avoid doing so. To clarify, in Mockito, this generally means avoiding using an ArgumentCaptor with Mockito.when. With stubbing, we should use an ArgumentMatcher instead.

Let’s look at a couple of reasons why we should avoid stubbing.

3.1. Decreased Test Readability

First, consider a simple test:

Credentials credentials = new Credentials("baeldung", "correct_password", "correct_key");
when(platform.authenticate(eq(credentials))).thenReturn(AuthenticationStatus.AUTHENTICATED);

assertTrue(emailService.authenticatedSuccessfully(credentials));

Here we use eq(credentials) to specify when the mock should return an object.

Next, consider the same test using an ArgumentCaptor instead:

Credentials credentials = new Credentials("baeldung", "correct_password", "correct_key");
when(platform.authenticate(credentialsCaptor.capture())).thenReturn(AuthenticationStatus.AUTHENTICATED);

assertTrue(emailService.authenticatedSuccessfully(credentials));
assertEquals(credentials, credentialsCaptor.getValue());

In contrast to the first test, notice how we have to perform an extra assert on the last line to do the same as eq(credentials).

Finally, notice how it isn’t immediately clear what credentialsCaptor.capture() refers to. This is because we have to create the captor outside the line we use it on, reducing readability.

3.2. Reduced Defect Localization

Another reason is that if emailService.authenticatedSuccessfully doesn’t call platform.authenticate, we’ll get an exception:

org.mockito.exceptions.base.MockitoException: 
No argument value was captured!

This is because our stubbed method hasn’t captured an argument. However, the real issue is not in our test itself but in the actual method we are testing.

In other words, it misdirects us to an exception in the test, whereas the actual defect is in the method we are testing.

4. Conclusion

In this short article, we looked at a general use case of using ArgumentCaptor. We also looked at the reasons for avoiding using ArgumentCaptor with stubbing.

As usual, all of our code samples are available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.