skip to content

How do you create and use an ArgumentCaptor, including the @Captor annotation?

level: middleimportance: must knowfreq 65%

answer

  1. forClass(Type.class) vs @Captor field
  2. @Captor avoids unchecked generics warning
  3. capture() goes inside verify(...)
  4. getValue() = single/last, getAllValues() = list
  5. Needs MockitoExtension / openMocks to init @Captor

basics

~10 s

Create one with ArgumentCaptor.forClass(Type.class), or declare a field with @Captor. Pass captor.capture() inside verify(mock).method(...), then read the captured value with captor.getValue().

solid answer

~40 s

There are two ways to make a captor. Inline: ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class). Or annotation-style: annotate a field with @Captor (ArgumentCaptor<User> captor) and let MockitoExtension / MockitoAnnotations.openMocks(this) initialize it. The @Captor route avoids the unchecked-generics warning you get with forClass on generic types like List<String>. To use it, call your code under test, then put captor.capture() in the argument slot of a verify call: verify(repository).save(captor.capture()). Mockito records the actual argument as a side effect of the verification. Afterward, getValue() returns the single captured value (or the last one), and getAllValues() returns every captured value as a List. capture() must appear inside a verify(...) or a stubbing call to actually record anything.

code

java · 15 lines
java
@ExtendWith(MockitoExtension.class)
class NotifierTest {
    @Mock EmailGateway gateway;
    @Captor ArgumentCaptor<Email> captor;
    @InjectMocks Notifier notifier;

    @Test
    void sendsWelcomeEmail() {
        notifier.welcome("[email protected]");

        verify(gateway).send(captor.capture());
        Email sent = captor.getValue();
        assertThat(sent.getTo()).isEqualTo("[email protected]");
    }
}

go deeper

for a junior

Knows the two creation styles and that capture() goes inside verify and getValue() reads it back.

for a middle

Writes the full @ExtendWith(MockitoExtension.class) + @Captor + verify + getValue flow correctly and explains the generics-warning reason to prefer @Captor.

for a senior

Explains capture() is a matcher that only works inside verify/stub, why mixing it with raw values throws, and getValue vs getAllValues semantics.

for a principal

Standardizes captor initialization across the suite (extension vs openMocks), guides on generic-type safety, and reviews tests for matcher-misuse pitfalls.

## Two ways to create a captor **Inline (forClass):** ```java ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class); ``` Simple, but for **generic** argument types like `List<String>` you can't write `List<String>.class`, so you pass `List.class` and get an *unchecked* compiler warning, and the captor's element type is unchecked. **Annotation (@Captor):** ```java @ExtendWith(MockitoExtension.class) class MyServiceTest { @Mock UserRepository repository; @Captor ArgumentCaptor<List<String>> captor; // full generic type, no warning @InjectMocks MyService service; } ``` The `@Captor` field is initialized by Mockito when the extension (JUnit 5 `MockitoExtension`) runs, or when you call `MockitoAnnotations.openMocks(this)` in a `@BeforeEach` (JUnit 4: `@RunWith(MockitoJUnitRunner.class)` or the rule). Because the field declares the **full generic type**, you avoid the unchecked warning of `forClass`. ## The usage pattern ```java // Arrange + Act: run the code that calls the mock. service.process("a", "b"); // Capture: put capture() in the argument position of verify. verify(repository).saveAll(captor.capture()); // Assert: read back. List<String> passed = captor.getValue(); assertThat(passed).containsExactly("a", "b"); ``` ### Why capture() must be inside verify (or a stub) `captor.capture()` is an **argument matcher**. Mockito matchers only work *during* a verification or stubbing call — they register themselves on Mockito's internal matcher stack and return a dummy value. The capturing side effect happens when Mockito processes that `verify`/stub. Calling `capture()` on its own line does nothing useful and can corrupt Mockito's matcher state (`InvalidUseOfMatchersException` on the next call). ## Reading the captured value(s) - `getValue()` — the captured argument. If the method was matched **multiple times**, it returns the **last** captured value. - `getAllValues()` — a `List` of **all** captured values, in call order. Use this with `verify(mock, times(n))` to inspect a sequence of calls. ## A complete example ```java @ExtendWith(MockitoExtension.class) class NotifierTest { @Mock EmailGateway gateway; @Captor ArgumentCaptor<Email> captor; @InjectMocks Notifier notifier; @Test void sendsWelcomeEmail() { notifier.welcome("[email protected]"); verify(gateway).send(captor.capture()); Email sent = captor.getValue(); assertThat(sent.getTo()).isEqualTo("[email protected]"); assertThat(sent.getSubject()).contains("Welcome"); } } ``` Key takeaways: `forClass` for non-generic types, `@Captor` for generic types (no warning), `capture()` only inside `verify`/stub, `getValue()` for one (or the last), `getAllValues()` for all.

  • Why prefer @Captor over forClass for a List<String> argument?
    Because you can't write List<String>.class, so forClass(List.class) produces an unchecked-generics warning and a loosely-typed captor. The @Captor field declares the full generic type with no warning.
  • What happens if you call captor.capture() outside a verify or stubbing call?
    It records nothing and can leave a dangling matcher on Mockito's internal stack, typically causing an InvalidUseOfMatchersException on the next Mockito call.

saying these in an interview costs you the question

  • Writing List<String>.class — illegal; use @Captor for generics
  • Calling capture() outside verify/stub (corrupts matcher state)
  • Forgetting to initialize @Captor (no MockitoExtension/openMocks → NPE)
  • Assuming getValue() returns the first call's value when there were many — it returns the last

context