skip to content

A mocked collaborator is called several times during one test and you need to assert on each argument it received. Explain how Mockito's ArgumentCaptor behaves across multiple invocations and how you would write that verification.

level: middleimportance: should knowfreq 52%

answer

  1. times(n) + getAllValues() for repeated calls
  2. getValue() = last element - silent trap
  3. List accumulates across verifications, never resets
  4. Sibling eq() narrows which calls get captured
  5. References not snapshots: reused builder = identical entries

basics

~10 s

Verify with a count - verify(mock, times(3)).send(captor.capture()) - then read captor.getAllValues(), a list in invocation order. getValue() alone returns only the last one, so asserting on it silently ignores earlier calls.

solid answer

~40 s

One captor collects every invocation the verification matches: ```java verify(mailer, times(3)).send(captor.capture()); List<Email> sent = captor.getAllValues(); assertThat(sent).extracting(Email::to) .containsExactly("a@x", "b@x", "c@x"); ``` `getAllValues()` preserves invocation order; `getValue()` is shorthand for the last element. If the verification uses `atLeastOnce()` or `times(n)` the captor accumulates all matched calls, so `containsExactly` is a real ordering assertion. Two things to watch. The list **accumulates across verifications** using the same captor, so a second `verify(...)` with the same captor appends rather than resets - declare a fresh captor per concern. And the captor stores references: if the production code reuses or mutates one builder object for every call, all captured entries can point at the same mutated instance, which shows up as three identical values. In that case capture a projection, assert inside a `doAnswer`, or make the payload immutable.

code

java · 12 lines
java
@Captor ArgumentCaptor<Email> captor;

@Test
void notifiesEveryRecipient() {
    service.broadcast(List.of("a@x", "b@x", "c@x"));

    verify(mailer, times(3)).send(captor.capture());

    assertThat(captor.getAllValues())
        .extracting(Email::to)
        .containsExactly("a@x", "b@x", "c@x");
}

go deeper

for a junior

Know that getAllValues() exists, returns invocation order, and that getValue() is only the last call.

for a middle

Pair times(n) with getAllValues() and an ordered assertion, and explain accumulation across verifications plus narrowing with eq() on sibling arguments.

for a senior

Raise the mutable-payload trap and the doAnswer snapshot workaround, and separate payload assertions from cross-mock ordering handled by InOrder.

for a principal

Ask whether asserting on every invocation is testing behaviour or implementation detail; prefer immutable outbound payloads and contract-level assertions so tests survive refactoring of call sequencing.

## Behaviour across invocations An `ArgumentCaptor` is not a single-value holder. Internally it keeps a list; each time its `capture()` matcher is evaluated against a matched invocation it appends the argument. So verifying a repeated call with one captor gives you the full sequence: ```java ArgumentCaptor<Email> captor = ArgumentCaptor.forClass(Email.class); verify(mailer, times(3)).send(captor.capture()); List<Email> sent = captor.getAllValues(); // size 3, invocation order Email last = captor.getValue(); // == sent.get(2) ``` `getValue()` is defined as the last captured value. This is the number-one source of weak tests: a service sends three notifications, the test asserts on `getValue()`, and two wrong payloads go unnoticed. If a method can be called more than once, verify with an explicit count and assert on `getAllValues()`. ## Ordering guarantees Values appear in the order the invocations happened on that mock, not in the order you wrote the verifications. For ordering *across different mocks*, `getAllValues()` cannot help - use `InOrder`: ```java InOrder order = inOrder(repo, mailer); order.verify(repo).save(any()); order.verify(mailer).send(any()); ``` A captor answers "what was sent"; `InOrder` answers "in what sequence relative to other collaborators". ## Accumulation and reuse The captor's list is never cleared. Two verifications with the same captor append to the same list: ```java verify(mailer).send(captor.capture()); // 1 value verify(auditMailer).send(captor.capture()); // now 2 values in one list ``` That is occasionally useful but usually a trap; prefer one captor per logical assertion, which `@Captor` fields make cheap. ## The mutable-argument trap Captors store references. Production code that reuses a single mutable object across calls: ```java Email buf = new Email(); for (String to : recipients) { buf.setTo(to); mailer.send(buf); } ``` yields three captured entries that are all the same instance with the final recipient. The test then reports something baffling like "expected [a, b, c] but was [c, c, c]". Remedies, in order of preference: make the payload immutable (usually the right production fix); assert inside a `doAnswer` that copies the relevant fields at call time; or capture a derived value by stubbing with an answer that records `email.to()`. ```java List<String> seen = new ArrayList<>(); doAnswer(inv -> { seen.add(inv.<Email>getArgument(0).to()); return null; }) .when(mailer).send(any()); ``` ## Filtering which calls get captured The captor records only invocations the verification actually matches. Sibling arguments narrow it: ```java verify(client, atLeastOnce()).post(eq("/orders"), captor.capture()); ``` captures bodies only for `/orders` calls. This is a clean way to slice a chatty collaborator - loose on the payload you want to inspect, strict on the routing arguments. Note that `atLeastOnce()` combined with assertions on `getAllValues().size()` re-implements a count check; prefer stating the count in `times(n)` so the failure message comes from Mockito's verification rather than from a size assertion. ## Choosing assertions With AssertJ, `extracting(...).containsExactly(...)` asserts values and order in one line and prints a readable diff. `containsExactlyInAnyOrder` when order is genuinely not part of the contract - do not assert order you do not care about, it makes the test brittle against harmless refactors. ## Interview framing Expected content: one captor collects all matched invocations, `getAllValues()` in invocation order, `getValue()` is just the last and is a trap for multi-call scenarios, verification count belongs in `times(n)`, the list accumulates across verifications, and captured values are references so mutation after the call is visible.

  • Captured values all look identical although the code clearly sends different payloads. What is happening?
    The production code is almost certainly reusing one mutable object for every call. The captor stores references, so every list entry points at the same instance and shows its final state. Either make the payload immutable, which is usually the correct production change, or record a snapshot of the fields inside a doAnswer at call time instead of capturing the object.
  • How do you assert the order of calls across two different mocks?
    An ArgumentCaptor only orders invocations on the mock it captured from, so use InOrder with the mocks involved: inOrder(repo, mailer) then order.verify(repo)... then order.verify(mailer).... Combine the two when needed - InOrder for sequence, captors for payload content - keeping each assertion focused on one property.

saying these in an interview costs you the question

  • Asserting only on getValue() when the collaborator is called multiple times
  • Expecting the captor's value list to reset between verifications
  • Assuming getAllValues() reflects the order the verify statements were written rather than invocation order
  • Using atLeastOnce() plus a size assertion instead of times(n), producing worse failure messages
  • Not realising captured values are live references that later mutation can change

context