skip to content

In Mockito you can constrain a mock's argument either with a custom predicate matcher passed to verify, or by capturing the argument and asserting on it afterwards. How do you choose between the two, and where does each belong?

level: seniorimportance: should knowfreq 45%

answer

  1. Matcher = selection, captor = inspection
  2. Stubbing cannot capture - matcher only
  3. Captor gives field-level failure diffs
  4. argThat prints <custom argument matcher> unless toString overridden
  5. Combine: eq/argThat on routing, captor on payload

basics

~20 s

Use a predicate matcher when the constraint selects which call you mean - especially during stubbing, where capturing is not available. Capture and assert when you want to inspect a rich payload, because assertion libraries report which field differed while a failed predicate only says no matching invocation was found.

solid answer

~50 s

The decision hinges on **selection versus inspection**. - **Stubbing** can only use matchers: `when(repo.find(argThat(q -> q.limit() > 10))).thenReturn(page)`. A captor cannot drive behaviour, and capturing inside `when(...)` records calls you never meant to inspect. - **Selecting one of several invocations** during verification is also matcher work: `verify(client).post(eq("/orders"), argThat(p -> p.isRetry()))`. - **Asserting on the content of the outbound payload** is captor work. `assertThat(captor.getValue().subject()).isEqualTo("Welcome")` fails with the actual and expected strings; the equivalent `argThat` fails with "Wanted but not invoked", listing raw invocations and leaving you to diff by eye. A practical rule: matchers narrow *which* interaction, captors assert *what* it contained. If a predicate grows more than one boolean condition, it has become an assertion - move it to a captor with a named-field assertion, or give the matcher a class with a descriptive `toString()`.

code

java · 4 lines
java
verify(client, atLeastOnce()).post(eq("/orders"), captor.capture());

assertThat(captor.getAllValues())
    .allSatisfy(p -> assertThat(p.orderId()).isPositive());

go deeper

for a junior

State the simple split: matchers to say which call you mean, captors to look at what was passed, and note that stubbing can only use matchers.

for a middle

Explain that capture() matches everything and stores, while argThat decides matching, and that failure-message quality is the practical tiebreaker.

for a senior

Argue the combination pattern - matcher on routing arguments plus captor on the payload - and the named-matcher-with-toString option for reusable conditions.

for a principal

Set the team default: verify interactions at boundaries with selective matchers and assert payloads with captors and explicit field-level assertions, avoiding whole-object comparisons that couple tests to unrelated schema growth.

## Two mechanisms, two jobs Both `argThat(predicate)` and `captor.capture()` are argument matchers evaluated when Mockito replays recorded invocations. The difference is what they do with the argument. - `argThat(p)` returns a boolean and thereby **decides whether the invocation matches**. Its verdict feeds verification (or stub selection). - `capture()` matches unconditionally and **stores** the argument. The decision moves out of Mockito and into your assertion library. ## Where captors cannot go Stubbing. `when(repo.find(...))` must decide *at call time* which stub applies, so it needs a predicate. Writing `when(repo.find(captor.capture())).thenReturn(page)` compiles and even works as a catch-all stub, but it captures every call, including those from unrelated code paths, and interleaves recording with behaviour setup. Keep capture in verification; use `argThat` (or `any`/`eq`) for stubbing. Mockito also documents a subtle risk: capturing during stubbing means values are recorded even when the test later fails for other reasons, so the captured list may not correspond to what you think you are inspecting. ## Where matchers are the wrong tool Failure diagnostics. Consider verifying a five-field event. With a predicate: ``` Argument(s) are different! Wanted: mailer.send(<custom argument matcher>); Actual invocations have different arguments: mailer.send(Email[[email protected], subject=Welcome!, ...]); ``` You learn that something did not match, not what. With a captor plus AssertJ: ``` expected: "Welcome" but was: "Welcome!" ``` On a failing CI run at 3am, that difference is the whole value proposition. Rich-object verification therefore belongs in captors. A middle path exists: implement `ArgumentMatcher` as a named class with a meaningful `toString()`, which Mockito prints instead of `<custom argument matcher>`. That recovers some readability while keeping the matcher's selection power - worth it for a matcher reused across many tests. ## Selection with several invocations When a chatty collaborator receives many calls, a predicate on the *routing* arguments plus a captor on the *payload* is the clean combination: ```java verify(client, atLeastOnce()).post(eq("/orders"), captor.capture()); assertThat(captor.getAllValues()).allSatisfy(p -> assertThat(p.orderId()).isPositive()); ``` Here the matcher does selection, the captor does inspection - each tool on its own job. ## Ordering and coupling considerations Captor assertions run after the interaction, so they can use any assertion style, including soft assertions and recursive field comparison (`usingRecursiveComparison`). That flexibility tempts over-assertion: capturing an event and asserting all fields couples the test to every future field addition. Assert the fields that carry the behaviour under test and let the rest float, or use recursive comparison with explicit ignored fields so the intent is visible. Matchers have the opposite coupling profile: because they are evaluated as part of verification, a mismatch is reported as "no such interaction", which occasionally masks a genuinely missing call as an argument problem or vice versa. When a captor test fails you can always distinguish "never called" (verification error) from "called with wrong content" (assertion error) - a real diagnostic advantage. ## Practical decision list 1. Stubbing behaviour, or choosing among overlapping stubs - **matcher**. 2. Narrowing which of several invocations you mean - **matcher** on the discriminating arguments. 3. Asserting the content of a payload with more than one interesting field - **captor**. 4. Reusable, semantically named condition used in many tests - **named `ArgumentMatcher` class with `toString()`**. 5. Both needed - matcher on routing arguments, captor on the payload, in the same `verify`. ## Interview framing Strong answers name stubbing as the hard constraint (captors do not work there), failure-message quality as the main practical driver, and the combination pattern. Weak answers claim one is simply better, or believe captors can drive stubbing behaviour.

  • Is capturing inside when(...) ever acceptable?
    It works mechanically but is discouraged. The captor then records every call that matches the stub, including invocations from code paths the test does not care about, so the captured list no longer corresponds to the interaction under assertion. It also mixes behaviour setup with recording, which hurts readability. Prefer argThat or any() for stubbing and capture during verification.
  • How would you get a readable failure message while still using a predicate matcher?
    Implement ArgumentMatcher in a named class rather than passing a lambda, and override toString() to describe the expectation, for example 'query with limit > 10'. Mockito prints that text where it would otherwise print <custom argument matcher>, so the failure states what was wanted. It still cannot show which field differed, which is why rich payloads remain captor territory.

saying these in an interview costs you the question

  • Claiming ArgumentCaptor can be used to select which stub applies
  • Preferring argThat for multi-field payload assertions and accepting the opaque failure output
  • Believing capture() filters invocations - it matches everything by design
  • Capturing inside when() as a default habit
  • Asserting every field of a captured event by default, coupling the test to unrelated future changes

context