Mockito ArgumentCaptor — Capturing Method Arguments

1 minute read

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

  1. @Captor + ArgumentCaptor lets you inspect exact objects passed to mocks.
  2. Use getValue() for single captures, getAllValues() for multiple.
  3. Prefer ArgumentCaptor when you need detailed assertions on captured objects.
  4. Only use capture() in verify() — never in when() stubbing.

What’s Next?

Next: Spy vs Mock — when to use partial mocking with @Spy.

Updated: