skip to content

When you assert in Mockito that no unverified calls are left on a mock, calls to stubbed methods keep tripping the check. How does ignoreStubs help, and when is exhaustive interaction verification the wrong tool entirely?

level: seniorimportance: nice to knowfreq 28%

answer

  1. ignoreStubs = stubbed calls pre-marked verified
  2. stops bookkeeping verifications
  3. works for ordered verification too
  4. exhaustive = pins whole protocol
  5. strict stubs already covers unused/mismatched stubbings

basics

~20 s

ignoreStubs(mock...) returns the same mocks with their stubbed invocations pre-marked as verified, so an exhaustive check only reports genuinely unexpected calls. It is the wrong tool when the collaborator's full protocol is not what the test is specifying - then a targeted positive and negative assertion pair ages far better.

solid answer

~60 s

`verifyNoMoreInteractions(ignoreStubs(repo, clock))` marks every invocation of a stubbed method as already verified, then applies the leftover check to what remains. Without it, any stubbed call the code legitimately makes to set the scenario up counts as unverified, and you end up writing verifications purely to silence the assertion. `ignoreStubs` also works with ordered verification for the same reason. The deeper point is that exhaustiveness is a design choice with a cost. It pins the *entire* set of calls to a collaborator, so an added metric, log or read fails a test whose subject was something else, and it fails at the exhaustive line rather than where behaviour changed. That is worth paying only when the completeness of the protocol is genuinely the specification - a narrow port, an outbound-effect boundary, a regression test written to pin "and nothing else happened". Elsewhere prefer a positive verification for the effect that matters plus a targeted negative one for the effect that must not happen; strict stubs already covers unused and mismatched stubbings.

code

java · 10 lines
java
@Test
void confirmingAnOrderPublishesOneEventAndNothingElse() {
    when(clock.instant()).thenReturn(FIXED);
    when(repo.findById("A")).thenReturn(Optional.of(order));

    service.confirm("A");

    verify(publisher).publish(any(OrderConfirmed.class));
    verifyNoMoreInteractions(ignoreStubs(repo, clock, publisher));
}

go deeper

for a junior

Know that calls to stubbed methods still count as interactions and that ignoreStubs exists to exclude them.

for a middle

Explain the mechanism - stubbed invocations pre-marked as verified - and show the one-line usage inside an exhaustive check.

for a senior

Lead with the tradeoff: exhaustiveness pins a collaborator's whole protocol, so justify it per test and name the symptoms of misuse.

for a principal

Position it as a suite-wide policy question about coupling budgets - which boundaries deserve protocol-complete specification, and how strictness defaults change what exhaustive checks still add.

## The problem ignoreStubs solves An exhaustive leftover check fails if any recorded invocation was not consumed by a prior verification. Recorded invocations include calls to methods you stubbed: stubbing decides what a call *returns*, it does not remove the call from the interaction record. So a test that stubs `clock.instant()` and `repo.findById(id)` so the scenario can run, then closes off interactions on those mocks, fails on precisely the calls it deliberately arranged. The naive fix is to add `verify(clock).instant()` and `verify(repo).findById(id)` - verifications that assert nothing anyone cares about and exist only to appease the check. They dilute the test: a reader can no longer tell which verifications are the specification and which are bookkeeping. `ignoreStubs(mock...)` is the intended fix. It takes mocks, marks all invocations of stubbed methods on them as verified, and returns those same mocks so they can be passed straight into the exhaustive check: ```java verifyNoMoreInteractions(ignoreStubs(repo, clock)); ``` Now the check reports only calls that were neither stubbed nor verified - which is exactly the class of surprise the assertion was written to catch. The same helper can be used when building an ordered verifier, for the same reason. A subtlety worth knowing: it ignores calls to *stubbed methods*, not calls with unstubbed arguments of a stubbed method. If `findById("A")` is stubbed and the code also calls `findById("B")`, whether that is ignored depends on the stubbing's matcher scope, so a surprising pass or failure there is usually a matcher question, not a bug in the helper. ## When exhaustive verification is the wrong tool Even with stub noise removed, exhaustiveness carries a specific coupling: the test now asserts the *complete* set of calls to a collaborator. Ask what that buys and what it costs. **It is justified when completeness is the behaviour.** "On a cache hit, the loader is not consulted at all." "When the circuit breaker is open, nothing goes downstream." "Confirming an order publishes exactly one event and touches nothing else." In those tests the absence of extra calls *is* the specification, and a new call genuinely should fail. **It is a brittleness smell when the test is about something else.** A service test whose subject is a computed result should not fail because someone added a metrics counter, a debug log, or an extra read on a repository. Symptoms that the tool is misapplied: - Failures cluster at exhaustive-check lines across many unrelated tests after one production change. - Tests carry verifications that assert incidental calls nobody would specify. - Changing a private implementation detail - caching a lookup, batching two calls into one - breaks tests whose observable behaviour is unchanged. - The mock is a rich collaborator with many methods rather than a narrow port. The underlying principle is that interaction tests couple to a collaborator's protocol. Coupling to the parts of the protocol that are contractual (the outbound effects) is useful; coupling to the whole surface freezes implementation shape and taxes every future change. ## What covers the gap instead Modern Mockito defaults to strict stubs under its JUnit 5 extension, which already fails tests for unused stubbings and reports argument mismatches. That removes a large slice of the historical "catch what I did not expect" motivation for exhaustive checks. What strict stubs does *not* catch is an extra call on a method nobody stubbed - so the residual value of exhaustiveness is real but narrow. A practical default: verify the effects that carry meaning, add a targeted negative assertion for effects that must not happen, use the whole-mock never-used assertion where a collaborator must stay untouched, and reserve exhaustive leftover checks with `ignoreStubs` for the handful of tests whose subject is protocol completeness. Reviewing an exhaustive check should always come with the question "what would this catch that the other assertions miss?" - if there is no answer, delete it.

  • Why does an exhaustive interaction check fail on a stubbed call at all, if you deliberately set that stubbing up?
    Because stubbing and interaction recording are separate concerns. Stubbing declares what a call returns; the mock still records the invocation when the code under test makes it. The exhaustive check asks whether every recorded invocation was consumed by a verification, and a stubbed-then-called method was not - hence ignoreStubs, which marks that whole class of invocations as already accounted for.
  • How would you decide, in review, whether a verifyNoMoreInteractions line should stay?
    I would ask what regression it would catch that the other assertions in the test would not. If the answer is a real defect - a duplicate publish, an unexpected write on a negative path - it stays, ideally with ignoreStubs so it reports only surprises. If the answer is vague, it is coupling the test to the collaborator's whole surface for no benefit, and I would replace it with a targeted negative assertion or remove it.

saying these in an interview costs you the question

  • Thinking ignoreStubs disables verification of stubbed methods you explicitly verified
  • Adding meaningless verify calls to silence an exhaustive check instead of excluding stubs
  • Claiming exhaustive verification is best practice for every test
  • Believing strict stubs makes all extra-call detection unnecessary
  • Using exhaustive checks on rich collaborators with large method surfaces

context