When testing real-world applications, we often need to verify not just whether a method was called, but also what arguments were passed to it. For example, you may want to check that a service correctly passed a constructed object to a repository.
This is where Mockito’s ArgumentCaptor
comes in. It allows you to capture arguments passed to a mocked method and then assert their properties.
What is ArgumentCaptor?
ArgumentCaptor
is a powerful Mockito utility for capturing and asserting method call arguments. Instead of only verifying that a method was called, you can inspect the actual values passed during execution.
Example: Capturing Arguments
Business Classes
class User {
private String name;
User(String name) { this.name = name; }
public String getName() { return name; }
}
interface UserRepository {
void save(User user);
}
class UserService {
private final UserRepository repository;
UserService(UserRepository repository) { this.repository = repository; }
void registerUser(String name) {
repository.save(new User(name));
}
}
Test with ArgumentCaptor
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository repository;
@InjectMocks
private UserService service;
@Test
void shouldCaptureArgument() {
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
service.registerUser("Alice");
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());
}
}
Here, ArgumentCaptor
captures the User
passed to repository.save()
, allowing us to assert its state.
Capturing Multiple Arguments
@Test
void shouldCaptureMultipleArguments() {
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
service.registerUser("Alice");
service.registerUser("Bob");
verify(repository, times(2)).save(captor.capture());
List<User> capturedUsers = captor.getAllValues();
assertEquals("Alice", capturedUsers.get(0).getName());
assertEquals("Bob", capturedUsers.get(1).getName());
}
You can use getAllValues()
to retrieve all captured arguments across multiple method calls.
Why ArgumentCaptor is Useful
- Deep Verification: Go beyond checking method calls to validating passed data.
- Readable Tests: Clear separation of verification and assertion.
- Works with Complex Objects: Useful when arguments are dynamically constructed.
Analogy: Think of ArgumentCaptor
as a camera—it captures a snapshot of what was passed into a method for later inspection.
Best Practices
- Use When Necessary — Prefer simpler
verify()
when possible. - Avoid Overuse — Too many captures may indicate overly complex interactions.
- Combine with Matchers — For flexible and readable tests.
- Assert Behavior, Not Implementation — Don’t test every detail, only critical state.
- Leverage Parameterized Tests — Combine with JUnit 5 for broader coverage.
Mockito + ArgumentCaptor + Testcontainers Example
In integration-heavy apps, capture arguments in unit tests, then validate behavior with Testcontainers in integration tests:
@Test
void shouldStoreUserInDatabase() {
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
assertTrue(postgres.isRunning());
}
}
This ensures mock verification is complemented with real-world validation.
Version Tracker
- Mockito Improvements: Enhanced support for capturing generics.
- JUnit 5 Transition: Full compatibility with
@ExtendWith(MockitoExtension.class)
. - Testcontainers Growth: Parallel adoption for integration tests.
Conclusion & Key Takeaways
Mockito’s ArgumentCaptor
is essential when verifying the exact data passed into mocks.
Key Takeaways:
- Use
ArgumentCaptor
to capture and assert method arguments. - Supports single and multiple argument captures.
- Best for validating stateful objects passed into dependencies.
- Combine with Testcontainers for full pipeline validation.
FAQ
1. What is ArgumentCaptor
in Mockito?
A utility for capturing and inspecting method arguments passed to mocks.
2. How do I use ArgumentCaptor?
Create with ArgumentCaptor.forClass()
, call capture()
inside verify()
, and use getValue()
or getAllValues()
.
3. Can I capture multiple arguments?
Yes, use getAllValues()
.
4. Should I always use ArgumentCaptor?
No, only when you need to inspect arguments.
5. How is it different from matchers like eq()
?
Matchers check expected values; captors capture the actual runtime values.
6. Can I use ArgumentCaptor with generics?
Yes, but you may need to provide type tokens.
7. Does ArgumentCaptor replace verify()
?
No, it complements verify()
by allowing argument inspection.
8. Can I use ArgumentCaptor with spies?
Yes, it works with both mocks and spies.
9. How does it help in CI/CD?
Improves test reliability by ensuring correct data flow.
10. What’s a common misuse of ArgumentCaptor?
Over-capturing and verifying trivial details instead of focusing on business logic.