skip to content

A teammate writes verify(mailer, timeout(500).never()).send(any()) in a Mockito test to prove no email is sent. What happens when that test runs, and how should the assertion be written instead?

level: seniorimportance: should knowfreq 25%

answer

  1. timeout + never = friendly-reminder misuse error
  2. zero interactions is the initial state
  3. use after(ms).never()
  4. atMost equally unusable under timeout
  5. latch + verifyNoInteractions beats waiting

basics

~10 s

It does not work as a check: Mockito refuses timeout() combined with never() and throws a friendly-reminder misuse exception. Use verify(mailer, after(500).never()).send(any()), which waits the whole window before concluding nothing was sent.

solid answer

~50 s

Mockito deliberately blocks that combination. Calling never() on a timeout() mode throws a FriendlyReminderException telling you to use after(...).never() instead — the test fails as a misuse error, not as a verification failure. The reason is semantic. timeout() is fail-fast on success, and a negative expectation is trivially satisfied at t=0: zero interactions is the initial state of every mock, so the loop would return green immediately and the timeout would be pure decoration. atMost() is meaningless under timeout() for the same reason. after(500).never() has the right shape: it re-evaluates for the full 500 ms and only then concludes, so a send at 300 ms fails the test. Even then, absence proven by waiting is only absence within that window. Where you can, drive the pipeline to a deterministic completion signal and use verifyNoInteractions(mailer) or verifyNoMoreInteractions(mailer) instead of paying wall-clock time.

code

java · 9 lines
java
// throws a Mockito misuse (friendly reminder) exception
verify(mailer, timeout(500).never()).send(any());

// correct: spends the full 500 ms, then concludes
verify(mailer, after(500).never()).send(any());

// better where a completion signal exists
assertTrue(pipelineDone.await(2, TimeUnit.SECONDS));
verifyNoInteractions(mailer);

go deeper

for a junior

Know the rule: negative checks use after(ms).never(); Mockito refuses timeout with never.

for a middle

Explain that timeout returns on first success and never() is already true at t=0, which makes the combination vacuous — hence the misuse exception.

for a senior

Add the cost and confidence analysis, and propose replacing the wait with a latch or synchronous executor plus verifyNoInteractions.

for a principal

Treat repeated absence-by-waiting as an architectural smell: argue for completion signals or injectable execution so absence can be asserted deterministically at scale.

## What actually happens Mockito.timeout(ms) returns a builder-style verification mode. Its never() (and atMost()) entry point does not build a mode at all — it throws a misuse exception whose message is a 'friendly reminder' pointing at after(...).never(). So the test errors out with a Mockito misuse error rather than telling you anything about the mailer. Candidates who claim the test 'passes and waits 500 ms' or 'flakily passes' have not tried it. ## Why the library forbids it The timed decorator has two stopping rules. timeout() returns on the first successful check; after() runs the whole window and reports the end state. A negative expectation is true before anything happens: a freshly created mock has zero interactions, so never() passes on the very first poll. timeout(500).never() would therefore return in microseconds and assert nothing about the next 500 ms — a test that looks careful and checks nothing. Rather than let that silently ship, Mockito rejects the combination. The same argument applies to atMost(n): 'no more than n' is satisfiable early and can only be falsified later, so it needs the full wait. Internally this is also why the timed loop treats some modes as non-recoverable: expectations that can only get worse over time are not retried into a pass. ## The correct forms verify(mailer, after(500).never()).send(any()) waits 500 ms, re-checking throughout, and fails if a send appears at any point in that window. verify(mailer, after(500).atMost(2)).send(any()) bounds the count over the window. Both cost 500 ms of wall clock on every run, which is the honest price of an absence claim made by waiting. ## The stronger alternative Absence over a time window is a weak claim: it says nothing about 501 ms. Prefer converting the wait into a happens-before edge. Typical patterns: await a CountDownLatch or Future that the pipeline completes, then assert verifyNoInteractions(mailer); inject a same-thread executor so the whole flow is synchronous and no waiting is needed at all; or assert a positive terminal outcome (an order marked CANCELLED, a metric incremented) that could only be reached on the path that must not email. These are deterministic, take no wall clock, and fail for the right reason. ## Suite-level consequence Negative async assertions are the biggest silent cost in a mock-heavy suite: each one is a fixed sleep. A handful is fine; dozens turn a fast unit suite into a minutes-long one, and shortening the windows to compensate quietly weakens every assertion. Treat after(...).never() as a marker that the code under test lacks a completion seam worth adding.

  • Why is 'zero interactions is the initial state' the core of the argument?
    Because timeout() stops at the first passing check, and a never() expectation already holds before the code under test has done anything. The loop would return on its first poll, so the timeout value would have no effect and the test would pass even if the call arrived a millisecond later. Only a full-window evaluation can give the assertion meaning.
  • How much confidence does after(500).never() really give you?
    Only that nothing happened within 500 ms of that point on that machine. It is a probabilistic claim whose strength scales with the window and inversely with machine load, and it costs 500 ms every run. A deterministic completion barrier followed by verifyNoInteractions is both stronger and free.

saying these in an interview costs you the question

  • Saying timeout(500).never() simply waits 500 ms and then checks
  • Saying it passes immediately but is otherwise fine
  • Not knowing after() exists and reaching for Thread.sleep before verifyNoInteractions
  • Treating after(ms).never() as proof the call can never occur
  • Shrinking the after window to speed up the suite without acknowledging the weaker assertion

context