A Mockito verification written as verify(repo).save(any()) still passes after a bug is introduced that saves the wrong object. Using Mockito's built-in matchers, how would you tighten it, and what do eq() and same() each buy you?
answer
- any() = called at all, never fails on wrong data
- eq() = equals(); no equals() override -> identity
- same() = ==; refEq() = reflective fields
- Mockito holds references: post-call mutation skews eq()
- loosest matcher that still fails on the real bug
basics
~20 sReplace any() with a matcher that constrains the value: eq(expected) compares with equals(), same(expected) requires the identical instance, refEq(expected) compares fields reflectively when the class has no equals(). Keep any() only for arguments the test genuinely does not assert.
solid answer
~50 sany() proves the method was called, nothing more, so it cannot fail on wrong data. Tighten it with the strongest matcher the argument supports. eq(expected) is the default choice - it uses equals(), so it needs the class to implement equals() meaningfully; on a class inheriting Object.equals it silently degrades to identity comparison. same(expected) asserts reference identity, which is the right assertion when you care that the exact instance was handed through untouched rather than copied or rebuilt. refEq(expected, "createdAt") compares field by field via reflection with named exclusions, useful for value-ish classes without equals(). One real trap: Mockito stores a reference to the argument, not a snapshot, so if the code mutates the object after the call, eq() compares against the mutated state and can pass or fail misleadingly. My rule is that arguments carrying the behaviour under test get an exact matcher; genuinely incidental ones stay loose.
code
java · 7 linesverify(repo).save(any());
verify(repo).save(eq(new Order("o-1", PENDING)));
verify(handler).forward(same(receivedEvent));
verify(repo).save(refEq(expectedOrder, "createdAt", "id"));go deeper
Say that any() only proves the call happened and that eq(expected) checks the value with equals().
Add same() versus eq(), the missing-equals() degradation, and refEq() for classes without equals().
Lead with the diagnosis - the test cannot fail as written - and give the rule for which arguments must be exact, plus the post-call mutation trap.
Talk about suite-level erosion: any() as the default fix creates green suites that catch nothing, and pair matcher policy with strict stubs so unmatched stubbing is also caught.
## Why any() cannot fail verify(repo).save(any()) asserts one thing: save was invoked once with some argument. Every property of that argument is unconstrained, so a regression that builds the wrong entity, drops a field, or passes a stale copy leaves the test green. Suites drift into this state because any() is the quickest way to make a red test pass, and nothing later flags the lost coverage. ## The built-in ways to tighten - eq(value) - the argument must equal value per equals(). This is the workhorse, but its strength is entirely inherited from the class. If the class does not override equals(), Object.equals is identity, so eq() quietly becomes same() and will fail for an equal-but-distinct instance. - same(value) - reference identity (==). Use it when the assertion really is 'this exact object was passed through', for example a handler that must forward the received event rather than reconstruct it, or a connection that must not be wrapped. - refEq(value, excludedFields) - reflective field-by-field comparison, with fields you can exclude by name (generated ids, timestamps). It rescues classes without equals(), at the cost of being brittle to field additions and of comparing private state. - isA(Foo.class) / any(Foo.class) - a weak but non-zero constraint: the argument is a non-null Foo. Worth using over any() for overloaded or polymorphic parameters, particularly exception types. - The String helpers - startsWith, endsWith, contains, matches(regex) - constrain part of a value when the whole of it is unstable, for instance a message containing a generated id. ## The mutation trap Mockito records the invocation by holding references to the arguments. Verification runs later, so the comparison sees the object's state at verification time, not at call time. If the code under test does repo.save(order) and then mutates order, eq(expectedOrder) is comparing against the mutated object. This produces both false passes and confusing false failures, and it is the reason people mistrust argument assertions on mutable entities. If your production code mutates after the call, assert on an immutable projection - an id, a copied field - rather than on the whole object. ## Deciding how tight A workable rule: for each argument, ask whether a wrong value there would be a bug the test claims to prevent. If yes, constrain it exactly. If no - a clock, a correlation id, a logger - leave it loose and say so with any(). The failure mode of over-tightening is real too: verifying every field of a large DTO couples the test to construction details, so any unrelated change to the object breaks dozens of tests and teams start deleting assertions wholesale. ## Backstops beyond matchers Strict stubs (MockitoSession or the JUnit 5 extension with Strictness.STRICT_STUBS) catch stubs whose arguments never matched, which is the complementary failure - a stub written with a wrong value silently never applying. Combining strict stubs with deliberate matcher choice is what keeps a mock suite honest. For assertions too complex to express as a built-in matcher, extracting the value and asserting on it separately is the usual alternative, but the decision starts here: use the loosest matcher that still fails on the bug you care about.
- eq(expected) fails even though the two objects look identical in the failure output. What is the likely cause?The class almost certainly does not override equals(), so eq() falls back to Object identity and two distinct instances never match, even with identical fields. The printed toString values look the same, which is what makes it confusing. Fix by implementing equals() where it belongs on the domain type, or use refEq() or an assertion on the extracted value.
- When is same() the right assertion rather than eq()?When identity is the contract: a component must forward the very object it received without copying or wrapping, or must hand back the same cached instance. same() also protects against defensive-copy regressions that eq() would happily accept. Everywhere else it is over-specified and brittle.
saying these in an interview costs you the question
- Treating verify(mock).save(any()) as a meaningful assertion about the saved data
- Not knowing eq() degrades to identity without an equals() override
- Assuming Mockito snapshots argument state at call time
- Verifying every field of a large DTO and calling the coupling free
- Loosening a failing matcher to any() as the standard fix