Mockito ArgumentCaptor — Capturing Method Arguments
Sometimes you need to verify exactly what object was passed to a dependency. ArgumentCaptor captures method arguments so you can assert on them.
The Problem
// How do you verify the exact User object saved to the repository?
userService.createUser("Alice", "alice@test.com");
// verify(userRepo).save(???) — what was passed?
Basic Usage
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock private UserRepository userRepo;
@Mock private EmailService emailService;
@InjectMocks private UserService userService;
@Captor
private ArgumentCaptor<User> userCaptor;
@Test
void shouldSaveUserWithCorrectFields() {
when(userRepo.save(any(User.class))).thenAnswer(inv -> {
User u = inv.getArgument(0);
u.setId(1L);
return u;
});
userService.createUser("Alice", "alice@test.com");
// Capture the argument
verify(userRepo).save(userCaptor.capture());
// Assert on captured value
User captured = userCaptor.getValue();
assertEquals("Alice", captured.getName());
assertEquals("alice@test.com", captured.getEmail());
assertNotNull(captured.getCreatedAt());
assertTrue(captured.isActive());
}
}
Without Annotation
@Test
void withoutAnnotation() {
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
userService.createUser("Bob", "bob@test.com");
verify(userRepo).save(captor.capture());
assertEquals("Bob", captor.getValue().getName());
}
getAllValues — Multiple Captures
@Test
void shouldSendMultipleEmails() {
ArgumentCaptor<String> emailCaptor = ArgumentCaptor.forClass(String.class);
notificationService.notifyAll(List.of("alice@test.com", "bob@test.com"));
verify(emailService, times(2)).sendEmail(emailCaptor.capture());
List<String> capturedEmails = emailCaptor.getAllValues();
assertEquals(2, capturedEmails.size());
assertEquals("alice@test.com", capturedEmails.get(0));
assertEquals("bob@test.com", capturedEmails.get(1));
}
Capturing Complex Objects
@Captor
private ArgumentCaptor<AuditEvent> auditCaptor;
@Test
void shouldLogAuditEvent() {
orderService.cancelOrder("ORD-123", "Customer request");
verify(auditLogger).log(auditCaptor.capture());
AuditEvent event = auditCaptor.getValue();
assertAll(
() -> assertEquals("ORDER_CANCELLED", event.getType()),
() -> assertEquals("ORD-123", event.getOrderId()),
() -> assertEquals("Customer request", event.getReason()),
() -> assertNotNull(event.getTimestamp())
);
}
Capturing Generic Types
@SuppressWarnings("unchecked")
@Test
void shouldSaveListOfItems() {
ArgumentCaptor<List<String>> listCaptor = ArgumentCaptor.forClass(List.class);
batchService.processItems(List.of("A", "B", "C"));
verify(repository).saveAll(listCaptor.capture());
assertEquals(3, listCaptor.getValue().size());
}
Real-World Example: Email Content Verification
@ExtendWith(MockitoExtension.class)
class PasswordResetServiceTest {
@Mock private UserRepository userRepo;
@Mock private TokenService tokenService;
@Mock private EmailService emailService;
@InjectMocks private PasswordResetService resetService;
@Captor private ArgumentCaptor<EmailMessage> emailCaptor;
@Test
void shouldSendResetEmailWithToken() {
User user = new User(1L, "Alice", "alice@test.com");
when(userRepo.findByEmail("alice@test.com")).thenReturn(Optional.of(user));
when(tokenService.generateResetToken(1L)).thenReturn("abc-123-token");
resetService.requestPasswordReset("alice@test.com");
verify(emailService).send(emailCaptor.capture());
EmailMessage email = emailCaptor.getValue();
assertEquals("alice@test.com", email.getTo());
assertEquals("Password Reset Request", email.getSubject());
assertTrue(email.getBody().contains("abc-123-token"));
assertTrue(email.getBody().contains("Alice"));
}
}
ArgumentCaptor vs argThat
| Use | When |
|---|---|
ArgumentCaptor |
Need to assert multiple fields on the captured object |
argThat() |
Simple inline check during verify |
// argThat — simple check
verify(repo).save(argThat(user -> user.getName().equals("Alice")));
// ArgumentCaptor — detailed assertions
verify(repo).save(captor.capture());
User u = captor.getValue();
assertEquals("Alice", u.getName());
assertEquals("alice@test.com", u.getEmail());
assertTrue(u.isActive());
Common Mistakes
| Mistake | Fix |
|---|---|
Using capture() in stubbing |
Only use in verify() calls |
Calling getValue() before verify |
Capture happens during verification |
Ignoring getAllValues() for multiple calls |
getValue() returns only the last captured value |
Key Takeaways
@Captor+ArgumentCaptorlets you inspect exact objects passed to mocks.- Use
getValue()for single captures,getAllValues()for multiple. - Prefer
ArgumentCaptorwhen you need detailed assertions on captured objects. - Only use
capture()inverify()— never inwhen()stubbing.
What’s Next?
Next: Spy vs Mock — when to use partial mocking with @Spy.