skip to content

How do you match a complex argument by some of its fields using argThat(), and what are the trade-offs versus ArgumentCaptor?

level: seniorimportance: should knowfreq 55%

answer

  1. argThat = lambda arg -> boolean (ArgumentMatcher)
  2. captor = capture then assert afterward
  3. argThat: weak failure messages unless custom toString
  4. captor: rich diagnostics, verification only
  5. both still obey the all-matchers rule; keep predicates pure

basics

~20 s

argThat() lets you pass a custom predicate that decides whether an argument matches — e.g. accept any Order whose total is over 100. Use it for inline rules; use ArgumentCaptor when you want to grab the actual argument and run rich assertions on it.

solid answer

~50 s

`argThat(predicate)` is the escape hatch for matching arguments by custom logic: you give it an `ArgumentMatcher<T>` (a lambda `arg -> boolean`) and Mockito uses it like any other matcher in `when`/`verify`. It shines when you only care about a subset of fields — `verify(repo).save(argThat(o -> o.getStatus() == PAID))`. Trade-offs versus `ArgumentCaptor`: argThat keeps the assertion *inline* with the verification and can re-match across multiple calls, but on failure it gives a poor diagnostic (just "wanted but not invoked") because it can't show how the actual value differed. ArgumentCaptor instead *captures* the real argument so you assert on it afterward with your normal assertion library, yielding rich failure messages and letting you inspect captured values across invocations. Rule of thumb: use argThat for simple boolean conditions or when you must match a specific call among several; use a captor when you want detailed, readable assertions on the object. Also keep argThat predicates side-effect-free and remember it still obeys the all-arguments-are-matchers rule.

code

java · 14 lines
java
// argThat: inline custom predicate (partial match on fields)
verify(orderRepo).save(argThat(o -> o.getStatus() == Status.PAID
                                    && o.getTotal().compareTo(BigDecimal.TEN) > 0));

// Mixing rule still applies: wrap literals in eq()
verify(emailService).send(eq("[email protected]"),
                          argThat(body -> body.contains("receipt")));

// ArgumentCaptor: capture then assert with rich diagnostics
ArgumentCaptor<Order> captor = ArgumentCaptor.forClass(Order.class);
verify(orderRepo).save(captor.capture());
Order saved = captor.getValue();
assertThat(saved.getStatus()).isEqualTo(Status.PAID);
assertThat(saved.getTotal()).isGreaterThan(BigDecimal.TEN);

go deeper

for a junior

Knows argThat() takes a condition and ArgumentCaptor grabs the real argument, even if unsure when to pick each.

for a middle

Writes correct argThat predicates and basic captor verifications, and respects the mixing rule with eq().

for a senior

Articulates the diagnostics/stubbing/multi-call trade-offs, keeps predicates pure, and improves argThat output with a custom ArgumentMatcher toString.

for a principal

Establishes team guidance (argThat for selection, captor for inspection), spots flaky tests from side-effecting predicates, and reviews tests for brittle equals-based matching that should be partial.

## The need: matching part of an object Sometimes a method takes a rich object and you only care about a few of its fields. Suppose `notificationService.send(EmailMessage msg)` and you want to assert it was called with a message **to** a particular address, ignoring subject and body. Exact-value matching (`eq(message)`) would force you to build an identical `EmailMessage` and rely on its `equals`. That's brittle. You want a *partial* match. ## argThat — custom matchers `ArgumentMatchers.argThat(ArgumentMatcher<T> matcher)` lets you supply your own rule. `ArgumentMatcher<T>` is a functional interface with one method `boolean matches(T argument)`, so you pass a lambda: ```java verify(notificationService).send(argThat(m -> m.getTo().equals("[email protected]"))); ``` Mockito evaluates the predicate against each actual argument; the call matches when it returns `true`. Because it is a matcher, it lives on the same thread-local stack as `any()`/`eq()` and obeys the **all-or-nothing** mixing rule (wrap any other literal arguments in `eq()`). Variants you'll meet: - `argThat(...)` — generic object matcher. - `intThat(...)`, `longThat(...)`, `charThat(...)` etc. — primitive-typed versions that avoid autoboxing-null pitfalls. - You can also implement `ArgumentMatcher<T>` as a named class and override `toString()` so the failure message describes the expectation (e.g. "order with status PAID"). This is the main lever to improve argThat's otherwise weak diagnostics. ## ArgumentCaptor — capture then assert An `ArgumentCaptor<T>` is the alternative tool. Instead of judging match/no-match inline, it **records** the actual argument so you can assert on it afterward: ```java ArgumentCaptor<EmailMessage> captor = ArgumentCaptor.forClass(EmailMessage.class); verify(notificationService).send(captor.capture()); EmailMessage sent = captor.getValue(); // the actual object assertThat(sent.getTo()).isEqualTo("[email protected]"); // rich assertion, rich failure ``` `captor.capture()` is itself a matcher (so it also obeys the mixing rule). `getValue()` returns the last captured argument; `getAllValues()` returns every captured argument across multiple matching invocations. ## The trade-offs, in detail **Diagnostics.** This is the biggest difference. When `argThat` fails, the predicate only returned `false`; Mockito can't show *why*, so you get a generic "Wanted but not invoked" / "Argument(s) are different" message with no field-level detail (unless you wrote a custom `ArgumentMatcher` with a descriptive `toString`). A captor + a normal assertion library (AssertJ/JUnit) produces a precise message like "expected to:'[email protected]' but was:'[email protected]'". For complex objects, captors win on debuggability. **Stubbing vs verification.** `argThat` works in **both** `when()` stubbing and `verify()`. A captor is really only meaningful in **verification** — capturing during stubbing is discouraged because stubs may run zero or many times, making the captured value misleading. **Multiple calls / re-matching.** `argThat` re-evaluates its predicate on every call, so it naturally selects matching calls among many. A captor instead records *all* of them; you then index into `getAllValues()`. Pick based on whether you want to *select* a call (argThat) or *inspect* a sequence (captor). **Side effects.** Keep `argThat` predicates pure — Mockito may invoke them multiple times and at surprising moments; mutating state inside the predicate causes flaky tests. **Readability.** A one-line boolean condition reads better inline as `argThat`. Several assertions on a captured object read better as a captor with explicit assert statements. ## Rule of thumb - Simple boolean condition, or you must pick one call out of several to stub/verify → **argThat** (add a custom `toString` if you need good failure output). - You want rich, readable field-level assertions, or to inspect the captured value/sequence → **ArgumentCaptor**. ## Bottom line `argThat` is the flexible, inline custom matcher; `ArgumentCaptor` is the capture-and-assert tool with superior diagnostics. They overlap, but you choose based on whether the test needs *matching logic* or *post-hoc inspection*.

  • Your argThat verification fails with an unhelpful message. How do you improve the diagnostics without switching to a captor?
    Replace the lambda with a named class implementing ArgumentMatcher<T> and override toString() to describe the expectation (e.g. 'order with status PAID and total > 10'); Mockito prints that in the failure. Or just switch to a captor for field-level assertion messages.
  • Can you use ArgumentCaptor to assert the order of arguments across multiple calls?
    Yes — verify(mock, times(n)).method(captor.capture()) then captor.getAllValues() returns every captured argument in invocation order, which you assert on as a list.

saying these in an interview costs you the question

  • Using ArgumentCaptor during stubbing — capture belongs in verification.
  • Putting side effects inside an argThat predicate (Mockito may call it multiple times).
  • Forgetting eq() on other arguments once you use argThat.
  • Expecting argThat to print field-level diffs without a custom ArgumentMatcher toString.
  • Reaching for argThat to do many assertions where a captor reads far better.

context