skip to content

A Mockito verification using a lambda-based custom argument matcher fails, and the report prints something like Test$$Lambda$14/0x0000000800c0a440@6d06d69c instead of anything readable. How do you make custom argument matchers produce diagnosable failure messages?

level: seniorimportance: should knowfreq 30%

answer

  1. report renders matcher.toString()
  2. lambda toString = synthetic class name
  3. named ArgumentMatcher + toString() description
  4. predicate returns boolean - no field-level diff
  5. actual side is only as good as the argument's toString()

basics

~20 s

Mockito prints the matcher's toString() in the 'Argument(s) are different' report, and a lambda's toString() is the synthetic class name. Implement ArgumentMatcher as a named class overriding toString() with a human description, or drop the matcher and assert on the extracted value instead.

solid answer

~50 s

When a verification fails, Mockito builds the 'wanted' line from each matcher's toString() and the 'actual' line from the recorded arguments. Built-in matchers print sensibly - any(), eq(1), isNull() - but argThat with a lambda prints the JVM's synthetic lambda name, so the report tells you the call did not match without saying what was expected. Two fixes. First, implement ArgumentMatcher as a small named class and override toString() to return a description like 'a PENDING order with total > 100'; the report then reads as a sentence. Second, decide whether a matcher is the right tool at all: a boolean predicate can only say yes or no, so even a well-named matcher will not tell you which field was wrong. When the assertion is rich, extract the argument and assert on it with your assertion library, which prints a field-level diff. I use named matchers when I need to discriminate between invocations, and value assertions when I need a good failure explanation.

code

java · 11 lines
java
class PendingOver100 implements ArgumentMatcher<Order> {
    @Override public boolean matches(Order o) {
        return o != null && o.status() == Status.PENDING
                && o.total().compareTo(new BigDecimal("100")) > 0;
    }
    @Override public String toString() {
        return "a PENDING order with total > 100";
    }
}

verify(repo).save(argThat(new PendingOver100()));

go deeper

for a junior

Know that Mockito prints matcher.toString() and that a named class overriding toString() fixes the message.

for a middle

Add why a lambda cannot be described, and that the argument's own toString() renders the actual side.

for a senior

Frame it as a diagnosability decision: predicates select invocations, extracted assertions explain values, and assertions must not live inside matchers.

for a principal

Set the team policy - inline lambdas only for trivial one-offs, named domain matchers for anything reused - and connect it to how quickly a CI failure can be triaged without rerunning locally.

## How Mockito builds the message On a failed verification Mockito prints a report with a 'Wanted' section and an 'Actual invocations' section. The wanted line is rendered by asking each registered matcher for its toString(). Built-in matchers implement it deliberately, so you see save(<any>) or log("CREATE", isNull()). A lambda passed to argThat inherits Object.toString(), which yields the synthetic class name plus identity hash - the unreadable string in the question. The verification is failing correctly; the diagnosis is simply missing. ## Fix one: a self-describing matcher org.mockito.ArgumentMatcher is an interface, not just a functional target, so nothing stops you from implementing it as a named class with a matches() method and a toString() that returns 'a PENDING order with total > 100'. The failure now reads Wanted: repo.save(a PENDING order with total > 100), and Actual shows the real order's toString(). Two supporting habits matter as much: give the domain object a meaningful toString(), because the actual side of the report is only as good as that, and keep the description phrased as a noun phrase so the generated sentence stays readable. A named matcher is also reusable across tests and gives the condition a name in your domain language. ## Fix two: do not use a matcher for a rich assertion A predicate returns a boolean. Even a perfectly described matcher cannot say 'the status was right but the total was 99.99'. If what you want is a field-level explanation, take the argument out of the verification and assert on it separately with your assertion library, which will produce a diff. The trade-off is that this happens after the fact: you lose the ability to distinguish between several invocations of the same method inside the verification itself, and you can no longer use the condition in stubbing. That is the real decision - matchers select invocations, assertions explain values. ## Things that make reports worse - Assertions inside the matcher. It is tempting, since an assertion library would produce the message, but Mockito may evaluate the matcher repeatedly and while composing its own report, and a thrown error escapes into the matching machinery rather than surfacing cleanly. - Over-broad predicates. A matcher that also matches wrong values does not fail at all, which is worse than a bad message. - Missing toString() on the argument type. The actual side then prints an identity hash and the report is useless in both directions. Records give you this for free; hand-written entities often do not. - Composing several conditions into one predicate. When it fails you cannot tell which conjunct was false. Splitting into AdditionalMatchers.and(matcherA, matcherB) with described matchers, or into separate assertions, keeps the failure localised. ## What good looks like For a suite you maintain, the pragmatic policy is: inline lambdas are acceptable for trivial, obvious conditions where a failure is self-evident; anything reused, or anything whose failure would need explaining, gets a named ArgumentMatcher with a toString(), or moves out of the verification into an explicit assertion on the value. Reviewers should treat a lambda matcher in a verification as a small debt, and a lambda matcher used in three tests as one worth paying off.

  • Why not just call an assertion library inside the matcher so it produces the message for you?
    Because Mockito may call matches() several times - once per candidate invocation and again while rendering the failure report - and an exception thrown from inside it escapes into the matching machinery instead of being reported as a clean assertion failure. The result is a misleading error and sometimes a swallowed one. Keep matchers pure and move rich assertions outside the verification.
  • The failure report shows the expected description clearly but the actual argument prints as com.acme.Order@1f2a3b. What now?
    The actual side is rendered with the argument's toString(), so the fix is on the domain type: implement a meaningful toString(), or use a record which generates one. Without it, no amount of matcher description will make the report diagnosable, and this is a common reason teams conclude that Mockito failures are unreadable.

saying these in an interview costs you the question

  • Assuming Mockito can describe a lambda predicate automatically
  • Putting assertions inside matches() to get a better message
  • Ignoring the argument type's toString(), which renders the actual side
  • Packing many conditions into one predicate so failures are unlocalised
  • Treating an unreadable message as a Mockito limitation rather than a missing description

context