skip to content

A Mockito verification fails with 'Wanted but not invoked' while another fails with 'Verification failed: too many actual invocations'. What does each message tell you, and how do you work from it to the root cause?

level: seniorimportance: should knowfreq 48%

answer

  1. zero interactions -> wiring or control flow
  2. actual invocations listed -> argument mismatch
  3. too many -> read the extra call's stack trace
  4. 'Argument(s) are different' = wanted/actual diff
  5. captors turn mismatches into field assertions

basics

~20 s

'Wanted but not invoked' means no recorded call matched your description - usually wrong arguments, wrong mock instance, or the code path never ran; the message lists the actual interactions. 'Too many actual invocations' means the method matched more often than the mode allowed, and prints the extra call's stack trace.

solid answer

~60 s

Both are `MockitoAssertionError` subclasses, and each points at a different class of cause. **Wanted but not invoked** - zero invocations matched the description. The message repeats the wanted invocation, then either "Actually, there were zero interactions with this mock" or a list of the calls that did happen. Zero interactions usually means the branch never executed, or the object under test holds a different instance than the mock you verified (missed injection, `new` inside the class, a spy that was not the one wired in). A list of near-miss calls usually means argument mismatch: `equals()` on a value object, a mutated argument, or a matcher that is looser or tighter than intended. **Too many actual invocations** - the method matched more times than `times(n)`/`atMost(n)` allowed. Mockito prints wanted count, actual count and the stack trace of the undesired invocation, so you can see which loop, retry or duplicate call produced it. The too-few counterpart reports the same shape in the opposite direction. Read the printed actual invocations first; they usually contain the whole answer.

code

java · 8 lines
java
// Fails with 'Argument(s) are different!' when Order lacks equals()
verify(repo).save(new Order("A", 2));

// Diagnose by capturing instead
ArgumentCaptor<Order> captor = ArgumentCaptor.forClass(Order.class);
verify(repo).save(captor.capture());
assertThat(captor.getValue().sku()).isEqualTo("A");
assertThat(captor.getValue().quantity()).isEqualTo(2);

go deeper

for a junior

Know the two headline messages and that Mockito prints the actual invocations it did see.

for a middle

Split 'wanted but not invoked' into the zero-interactions case and the argument-mismatch case, and name typical causes of each.

for a senior

Present a repeatable triage method - exception type, then message shape, then the extra call's stack trace - and know when the correct fix is to change the test rather than the code.

for a principal

Talk about what these failures reveal structurally: unverifiable collaborators created inside classes, shared mock state across tests, and counts asserted at the wrong altitude.

## The diagnostics are the tool Mockito invests heavily in failure messages because interaction assertions fail for reasons that are invisible from the assertion line alone. Every verification failure is a `MockitoAssertionError` subclass, and the subclass identity is a diagnosis in itself. ## Wanted but not invoked Raised when the mode required at least one matching invocation and none was found. The message has two shapes and the difference matters more than anything else on the screen: **Shape A - "Actually, there were zero interactions with this mock."** Nothing at all touched this mock. Candidate causes, in the order worth checking: - The code path never ran: a guard returned early, an exception was swallowed, a condition you assumed was true was not. - The mock is not the collaborator the code uses. The class under test constructed its own instance with `new`, injection did not happen (a missing `@InjectMocks`, a field the framework could not fill, a constructor argument that quietly took a different object), or you verified the mock from a different test fixture. - The call happened on another thread that had not finished when verification ran. (Asynchrony has its own tooling; the signature clue is that the assertion passes when you add a sleep.) **Shape B - a list of actual invocations.** The mock was used, but nothing matched your description. Now the mismatch is visible: compare the printed arguments against what you asked for. Recurring causes are a value object without `equals`/`hashCode` (so equality is identity), an argument the production code mutated after the call so the recorded reference no longer equals your expectation, an overload you did not expect, or matcher drift - `any()` where you meant `eq()`, or a captor-less `argThat` predicate that is subtly wrong. When arguments are complex, switching the assertion to an argument captor and asserting on the captured object's fields turns a cryptic mismatch into a normal field-by-field comparison. ## Too many actual invocations Raised when more matching invocations exist than the mode permits - exact `times(n)` exceeded, or `atMost(n)` exceeded. The message states wanted and actual counts and, crucially, prints the stack trace of the *undesired* invocation. That trace is the fastest route to the cause: it names the loop, the retry wrapper, the event listener registered twice, or the second code path that also calls the collaborator. Common real causes: a retry policy the test did not account for; a listener or subscriber registered on every call instead of once; a bulk operation that fell back to per-item calls; state leaking between tests because the mock is shared (a `static` mock, a `@Mock` on a class with per-class lifecycle, or a manually created mock reused across methods without `clearInvocations`). ## The too-few counterpart When some but not enough invocations matched, Mockito reports a too-few-invocations failure (`TooFewActualInvocations`, historically named `TooLittleActualInvocations`) with wanted count, actual count and the matching call sites. It usually means the loop ran fewer iterations than expected, or a retry short-circuited. ## Two more messages worth recognising - **Never wanted here, but invoked** - the `never()` violation, with both the verification site and the offending call site. - **Argument(s) are different!** - emitted when Mockito finds an invocation of the same method with different arguments; it prints a wanted-versus-actual diff, which is the friendliest possible form of Shape B. ## A working method 1. Read the exception type first: not-invoked versus too-many versus argument-different already partitions the search space. 2. If it is not-invoked, decide between "zero interactions" (wiring or control flow) and "other interactions listed" (argument matching). These have disjoint fixes; guessing wastes the most time here. 3. If it is too-many, jump straight to the printed stack trace of the extra call. 4. Only after that, consider whether the *test* is wrong: an exact count pinned to an incidental number, or a verification asserting something the code was never meant to do. A final note on hygiene: verification errors are assertion errors, so a test that swallows `Throwable` around the code under test can hide them, and a verification placed inside a lambda that never runs asserts nothing at all.

  • A verification says 'Actually, there were zero interactions with this mock', yet a debugger shows the collaborator method executing. What is the most likely explanation?
    The object executing is not the mock you verified. Typically the class under test created its own collaborator with new, or dependency injection wired a real bean while the test verified an unrelated @Mock field. Check how the instance under test received its collaborator; if it constructs it internally, the design needs the dependency passed in before any interaction test can work.
  • How do you tell a genuine too-many-invocations bug from a test that pinned an incidental number?
    Ask whether a repeated call is observable or harmful in production. A second charge, email or event publish is a defect and the test is right to fail. A second read of a clock, cache or logger is usually harmless, which means the exact count was documenting implementation shape - there the fix is to relax or delete the count rather than to change production code.

saying these in an interview costs you the question

  • Treating every 'wanted but not invoked' as an argument problem without checking whether any interactions happened at all
  • Ignoring the printed stack trace of the extra invocation on a too-many failure
  • Adding a sleep to make a verification pass instead of identifying the missing synchronisation
  • Assuming the mock and the collaborator the code uses are the same instance without checking injection
  • Loosening the verification to any() to make the message go away

context