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.
2. Using ArgumentCaptor
ArgumentCaptor allows us to capture an argument passed to a method in order 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. In order 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:
@RunWith(MockitoJUnitRunner.class)
public 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
Secondly, let's add a new ArgumentCaptor field of type Email to store our captured argument:
@Captor
ArgumentCaptor<Email> emailCaptor;
2.3. Capture the Argument
Thirdly, let's use Mockito.verify with the ArgumentCaptor to capture the Email:
Mockito.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
public void whenDoesSupportHtml_expectHTMLEmailFormat() {
String to = "info@baeldung.com";
String subject = "Using ArgumentCaptor";
String body = "Hey, let'use ArgumentCaptor";
emailService.send(to, subject, body, true);
Mockito.verify(platform).deliver(emailCaptor.capture());
Email value = emailCaptor.getValue();
assertEquals(Format.HTML, value.getFormat());
}
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");
Mockito.when(platform.authenticate(Mockito.eq(credentials)))
.thenReturn(AuthenticationStatus.AUTHENTICATED);
assertTrue(emailService.authenticatedSuccessfully(credentials));
Here, we use Mockito.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");
Mockito.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 in order to do the same as Mockito.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 of the line we use it on, which reduces 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 actual issue is not in our test itself, but 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 tutorial, 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.