skip to content

In the Mockito mocking library, how do you assert that a collaborator was not touched at all during a test, and how is that different from asserting that one particular method was not called?

level: juniorimportance: must knowfreq 52%

answer

  1. verifyNoInteractions(mock) = zero calls of any kind
  2. varargs: several mocks at once
  3. never() = one described call only
  4. stubbed-and-called still counts as interaction
  5. verifyZeroInteractions = deprecated old name

basics

~20 s

Use verifyNoInteractions(mock) - it fails if any method at all was called on that mock. Asserting one method was not called is verify(mock, never()).method(args), which is scoped to that method and those arguments and says nothing about the rest of the mock.

solid answer

~50 s

`verifyNoInteractions(mailer)` asserts that the mock recorded **zero invocations of any kind**. It takes varargs, so `verifyNoInteractions(mailer, auditLog)` covers several collaborators at once, and it fails with a no-interactions-wanted error that prints the unwanted call and its stack trace. The contrast is scope. `verify(mailer, never()).send(eq("[email protected]"), any())` constrains exactly one described invocation: a call to `send` with other arguments, or a call to `close()`, still passes. Whole-mock and per-invocation assertions answer different questions, and choosing the wrong one either under-specifies or over-specifies. Use the whole-mock form for genuine "this side effect must not happen" cases: a validation failure must not reach the repository, a disabled feature flag must not call the payment gateway. Note that it means *no interactions since the mock was created*, so it belongs on a mock that is fresh for the test, and it is unrelated to stubbing - a stubbed method that the code actually calls still counts as an interaction.

code

java · 14 lines
java
@Test
void rejectsInvalidOrderWithoutTouchingCollaborators() {
    assertThrows(ValidationException.class, () -> service.place(invalidOrder));

    verifyNoInteractions(repo, paymentGateway);   // nothing at all
}

@Test
void readOnlyQueryNeverWrites() {
    service.findOrder("A");

    verify(repo).findById("A");                    // reads are expected
    verify(repo, never()).save(any());             // writes are not
}

go deeper

for a junior

Recall the API name and that it means zero calls of any kind on that mock; contrast it with never() on a single method.

for a middle

Explain what counts as an interaction (stubbing setup does not, an actual call to a stubbed method does) and name concrete negative-path cases where it is the right assertion.

for a senior

Add scope discipline - forbid the specific effect when the collaborator is otherwise used - and warn about vacuous passes on mocks that were never wired in.

for a principal

Treat negative assertions as part of the contract at a boundary: which outbound effects must provably not occur, and how that shapes what the collaborator interface should even expose.

## Two different questions Every Mockito assertion has a scope. A per-invocation assertion asks "did *this call with these arguments* happen (this many times)?". A whole-mock assertion asks "was this object used *at all*?". `verifyNoInteractions` is the second kind. ```java verifyNoInteractions(mailer); // nothing at all on mailer verify(mailer, never()).send(any()); // no send(...) call specifically ``` The first fails if the code called `mailer.close()`. The second does not. Candidates routinely conflate them, which produces tests that claim more than they check. ## What counts as an interaction Any method invocation recorded on the mock. That includes methods whose return values were stubbed - stubbing does not exempt a call from being an interaction, it only decides what the call returns. It includes calls made from any thread, in registration order. It does not include configuring the mock itself: the `when(mock.foo()).thenReturn(x)` setup line is recognised as stubbing and removed from the interaction record, which is why a stubbed-but-never-called mock still passes `verifyNoInteractions`. Because the check spans the mock's entire life, it only makes sense on a mock that is fresh for the test - the standard per-test `@Mock` field. On a mock shared across tests (a static field, or one created in a class-level fixture), earlier tests' calls remain recorded and the assertion becomes order-dependent unless you clear invocations. ## When it earns its place The strong cases are negative-path specifications where the *absence* of an outbound effect is the behaviour: - Input validation fails, so nothing must reach the repository. - A feature flag is off, so the payment gateway must not be called. - A cache hit means the loader must not be consulted. - An idempotency check detects a replay, so no event may be published. In each case the effect has no return value to assert on, and "nothing happened" is precisely the contract. It is also cheap and stable: no argument matching, no counts, nothing that a harmless refactor will move. ## When the narrower form is right If the collaborator is legitimately used for something else in the same code path, whole-mock exclusion is simply false. A service may read from the repository and must merely not write to it; that is `verify(repo, never()).save(any())`, not `verifyNoInteractions(repo)`. Reaching for the whole-mock form there produces a test that fails the moment a read is added. ## The failure it produces A violation raises a no-interactions-wanted error listing the unwanted invocation with its stack trace, so you can see immediately which call broke the expectation. If several mocks are passed, the message identifies which one was touched. ## A note on the older name Older code uses `verifyZeroInteractions(mock)`, which was deprecated in favour of `verifyNoInteractions` because the old name read as if it were about counts rather than about the mock as a whole. They behave the same; new code should use `verifyNoInteractions`. Mentioning that history is a reasonable signal of familiarity, but writing the deprecated form in new tests is not. ## Placement in the test Like all verification, it goes after the act phase. A common structural mistake is asserting it on a mock that the class under test never received at all - then the assertion passes trivially and proves nothing, because an unused mock and an unwired mock look identical from the outside. Pairing the negative assertion with at least one positive assertion about what the code *did* do protects against that vacuous pass.

  • Does stubbing a method with when(...).thenReturn(...) cause verifyNoInteractions to fail?
    No. Mockito recognises the stubbing call itself as configuration and does not record it as an interaction, so a mock that was stubbed but never used still passes. What does fail the assertion is the code under test actually invoking that stubbed method during the act phase - stubbing controls what a call returns, it does not exempt the call from the interaction record.
  • A service must read from the repository but must not write to it. Which assertion do you use and why?
    verify(repo, never()).save(any()) - the narrow, per-invocation form. verifyNoInteractions(repo) would be factually wrong, since the read is expected and would fail the assertion. The rule is to match the scope of the assertion to the scope of the contract: forbid the specific effect, not the whole collaborator.

saying these in an interview costs you the question

  • Believing verify(mock, never()).foo() proves the mock was untouched overall
  • Thinking a stubbed method that the code calls does not count as an interaction
  • Using verifyNoInteractions on a collaborator the code legitimately reads from
  • Applying it to a mock shared across tests without clearing invocations
  • Writing verifyZeroInteractions in new code as if it were the current API

context