skip to content

Explain what Mockito's ArgumentCaptor is for, how you create one and wire it into a verification, and how you read the captured value afterwards.

level: juniorimportance: must knowfreq 70%

answer

  1. forClass or @Captor -> capture() inside verify -> getValue()
  2. capture() IS a matcher: siblings need eq()
  3. getValue = last, getAllValues = all in order
  4. @Captor keeps generics that forClass loses
  5. Nothing captured if the verify itself fails

basics

~10 s

An ArgumentCaptor grabs the actual argument a mock received so you can assert on it. Create it with ArgumentCaptor.forClass(X.class) or @Captor, pass captor.capture() inside verify(...), then read captor.getValue() and assert normally.

solid answer

~40 s

An `ArgumentCaptor` records the real argument passed to a mock so the test can inspect it with ordinary assertions instead of expressing the expectation as a matcher. Three steps: ```java ArgumentCaptor<Email> captor = ArgumentCaptor.forClass(Email.class); verify(mailer).send(captor.capture()); assertThat(captor.getValue().subject()).isEqualTo("Welcome"); ``` `capture()` is itself an argument matcher (it matches anything and stores the value), so it obeys the all-or-none rule: any sibling literal in that call must be wrapped in `eq()`. Alternatively declare `@Captor ArgumentCaptor<Email> captor;` and let `MockitoExtension` (or `MockitoAnnotations.openMocks(this)`) initialise it - this also preserves generic type parameters, which `forClass` cannot. `getValue()` returns the **last** captured argument; `getAllValues()` returns every captured value in invocation order. Capturing after a failed verification is pointless - if `verify` fails, nothing is captured.

code

java · 15 lines
java
@ExtendWith(MockitoExtension.class)
class SignupServiceTest {
    @Mock Mailer mailer;
    @Captor ArgumentCaptor<Email> captor;
    @InjectMocks SignupService service;

    @Test
    void sendsWelcomeMail() {
        service.signUp("[email protected]");

        verify(mailer).send(captor.capture());

        assertThat(captor.getValue().subject()).isEqualTo("Welcome");
    }
}

go deeper

for a junior

Show the three-step pattern - forClass or @Captor, capture() inside verify, getValue() plus assertions - and mention that capture() counts as a matcher.

for a middle

Add getAllValues() for repeated calls, why @Captor is preferred for generic types, and that capture happens during verification so ordering matters.

for a senior

Bring in failure-message quality as the reason to prefer capture-and-assert for rich payloads, plus the mutable-argument trap where the captor holds a reference.

for a principal

Frame it as a test-design choice: captors assert on outbound payloads at a module boundary, which is valuable for contract-shaped collaborators, but heavy captor use often signals verification of implementation detail rather than behaviour.

## The problem it solves Verification normally answers "was this method called with something matching X?". When the argument is a rich object built inside the code under test - an event, a DTO, a request - encoding the whole expectation as a matcher is awkward and the failure message is poor. `ArgumentCaptor` inverts the flow: let the call match loosely, keep the actual object, and assert on it with your normal assertion library. ## Creating a captor Two ways: ```java // 1. explicit ArgumentCaptor<Email> captor = ArgumentCaptor.forClass(Email.class); // 2. annotation - needs MockitoExtension or MockitoAnnotations.openMocks(this) @Captor ArgumentCaptor<Email> captor; ``` The annotation form is preferred for generic types: `ArgumentCaptor.forClass(List.class)` cannot express `List<String>` and forces an unchecked assignment, while `@Captor ArgumentCaptor<List<String>> captor` reads the type from the field's generic signature. Recent Mockito versions also offer `ArgumentCaptor.captor()`, which infers the type from the assignment target. ## Using it `captor.capture()` is called *inside* the mocked invocation, in the position of the argument you want: ```java verify(mailer).send(captor.capture()); verify(repo).update(eq(42L), captor.capture()); // eq() required: capture() is a matcher ``` That is the key mental model: **`capture()` is an argument matcher** that matches any value and, as a side effect, records it. Because it participates in the matcher stack, mixing it with raw literals in the same call throws `InvalidUseOfMatchersException`; wrap the literals in `eq()`. Capture happens during verification, when Mockito replays the recorded invocations and evaluates matchers against them. So order matters: the captor is empty until the `verify` line executes, and if the verification fails (wrong number of calls, other argument mismatch), no assertions on the captured value will be meaningful - fix the verification first. ## Reading values - `getValue()` - the last captured argument. If the verification matched several invocations, this is the most recent one. - `getAllValues()` - a `List` of every value captured by this captor, in invocation order, across all verifications that used it. ```java verify(mailer, times(2)).send(captor.capture()); List<Email> sent = captor.getAllValues(); assertThat(sent).extracting(Email::to).containsExactly("a@x", "b@x"); ``` Calling `getValue()` when nothing was captured throws `MockitoException` ("No argument value was captured") - a common symptom of asserting before verifying, or of verifying a method that was never called. ## A worked example ```java @ExtendWith(MockitoExtension.class) class SignupServiceTest { @Mock Mailer mailer; @Captor ArgumentCaptor<Email> captor; @InjectMocks SignupService service; @Test void sendsWelcomeMail() { service.signUp("[email protected]"); verify(mailer).send(captor.capture()); Email sent = captor.getValue(); assertThat(sent.to()).isEqualTo("[email protected]"); assertThat(sent.subject()).isEqualTo("Welcome"); } } ``` The failure output here names the exact field that differed, whereas a matcher-based verification would report only that no matching invocation was found. ## Gotchas to mention **Mutable arguments.** The captor stores a reference, not a snapshot. If production code reuses or mutates the object after the call (a pooled builder, a cleared list), the captured value reflects the *final* state. Assert on a copy or capture a defensive projection when that bites. **Captor in stubbing.** `when(mock.f(captor.capture())).thenReturn(x)` is legal but captures on every matching call including ones you did not intend to inspect, and mixes concerns; capturing belongs in verification. **One captor, one argument position.** Reusing a captor for two different parameters in the same call accumulates both into `getAllValues()` in registration order, which is confusing - declare one captor per parameter. ## Interview framing A junior answer covers create-capture-assert. A stronger one adds that `capture()` is a matcher (hence `eq()` for siblings), that `getValue()` is the last value while `getAllValues()` covers repeated calls, and that `@Captor` exists mainly to carry generics.

  • What does getValue() return if the mock method was called three times and you verified with times(3)?
    It returns the argument from the last of the three matched invocations. To inspect all of them use getAllValues(), which returns them as a list in invocation order. Asserting on getValue() alone in a multi-call scenario is a common bug because it quietly ignores the earlier calls.
  • Why does Mockito throw 'No argument value was captured' even though the production code clearly calls the method?
    Because capture happens while the verification runs, so either getValue() was called before the verify line, or the verification did not match the invocation - a different overload, an argument mismatch on a sibling parameter, or a call on a different mock instance. Fix the verification first and the capture follows; a failing verify captures nothing.

A captor is a photocopier at the door: instead of describing in advance what the letter should say, you let any letter through, keep a copy, and read it afterwards.

saying these in an interview costs you the question

  • Calling captor.getValue() before the verify line and expecting a value
  • Passing a raw literal alongside captor.capture() in the same call and being surprised by InvalidUseOfMatchersException
  • Believing getValue() returns the first captured argument rather than the last
  • Using ArgumentCaptor.forClass(List.class) for List<String> and assuming the generic type is enforced
  • Thinking the captor snapshots the object, when it stores a reference that later mutation can change

context