In MockServer, how does VerificationTimes.never differ from verifyZeroInteractions on a catalogue test?
answer
- one is scoped, the other is total
- a pattern is something you can mistype
- no traffic at all versus not this call
- never() is bound to one definition
- verifyZeroInteractions has no pattern to get wrong
basics
~20 sMockServer's VerificationTimes.never asserts that no request matching one definition arrived, so it says nothing about other traffic. verifyZeroInteractions asserts the journal is empty outright. A never assertion built on a mistyped path passes vacuously; the empty-journal check cannot.
solid answer
~40 sBoth are negative assertions, and they are scoped differently. `MockServerClient.verify(definition, VerificationTimes.never())` asserts that the journal holds no request matching *that definition* — no `DELETE /catalogue/v1/holds/H-88213`, say — while every other request the client made is ignored. `MockServerClient.verifyZeroInteractions()` asserts something much stronger: the journal is empty, so the client never touched the catalogue at all. The scoping difference is also the trap. A negative assertion cannot distinguish "it did not happen" from "my definition was wrong", so a `never()` built on a mistyped path or the wrong method passes for the wrong reason and keeps passing forever. `verifyZeroInteractions()` has no pattern to get wrong, which makes it the safer choice whenever the claim really is "no traffic". WireMock has no `never()` at all; its equivalent is `exactly(0)`.
go deeper
Know that both calls exist and what each covers: never() is tied to one request definition, while verifyZeroInteractions covers the whole journal and takes no argument.
Explain why a negative assertion is only as good as its definition, and why an empty-journal check has no pattern that can silently drift away from the traffic it was written for.
Show how you make a never() trustworthy in a real suite — sharing the definition with a positive assertion so a typo fails loudly — and when you would widen to verifyZeroInteractions instead.
Own the standard for negative assertions across a suite, because a vacuous never() is worse than no assertion at all: it reports coverage that does not exist and nobody ever revisits it.
## Two different negatives MockServer offers two ways to assert that something did not happen, and they are scoped differently. `MockServerClient.verify(definition, VerificationTimes.never())` asserts that the journal holds **no request matching that definition**. Everything else the client did is invisible to it. If the definition names `DELETE /catalogue/v1/holds/H-88213`, the assertion says only that this particular cancellation never went out; the client may have placed forty holds and run a hundred searches, and the assertion still passes — correctly. `MockServerClient.verifyZeroInteractions()` asserts that the **journal is empty**. It takes no request definition because it has no pattern to apply: any request, on any path, with any method and any body, fails it. It is the assertion for "the catalogue was not touched at all", which is what you want for a code path that should have been short-circuited by a cache, a feature flag or a validation failure before any HTTP call was attempted. | assertion | scope | fails when | |---|---|---| | `verify(definition, VerificationTimes.never())` | requests matching that one definition | any matching request sits in the journal | | `verifyZeroInteractions()` | the entire journal | any request at all reached the server | ## The vacuous pass Every negative assertion shares one weakness: it cannot distinguish "the thing did not happen" from "my description of the thing was wrong". A positive assertion is self-checking — write the path wrong and it fails immediately, loudly, on the very first run. A negative assertion written against a wrong path passes on the first run and every run after it, and it keeps passing after the cancellation endpoint starts being called in error, because the pattern it watches never described that endpoint in the first place. Concretely: `never()` on `request().withPath("/catalogue/v1/hold/H-88213")` — singular `hold` — is green forever. Nothing in the test, the build or the report distinguishes it from a correct assertion. The scoping that makes `never()` useful is exactly what makes it fragile. ## Making a negative assertion trustworthy 1. Build the request definition once, in a shared helper or constant, and use the very same object in a test where the call *is* expected. If the path is wrong, that positive test fails and tells you. 2. Where the claim really is "no traffic at all", prefer `verifyZeroInteractions()`. It has no pattern, so it has no pattern to get wrong. 3. Prove the negative can fail: temporarily make the code under test send the request and confirm the assertion goes red. A negative assertion nobody has ever seen fail is unverified test code. 4. Pair the negative with a positive on the same run wherever you can — "the availability read happened once and the cancellation never did" is a far stronger statement than either half alone. ## What neither one proves - Neither says anything about traffic that went somewhere else. A client pointed at a different host, port or base URL leaves this journal empty, and `verifyZeroInteractions()` then passes for entirely the wrong reason. - Neither knows whether an expectation matched. The journal records arrivals, so a request that no expectation answered is still an interaction and still fails `verifyZeroInteractions()`. - Neither covers work that happens after the assertion runs. An asynchronous cancellation dispatched a moment later is not in the journal yet, so both negatives pass and the defect ships. ## Choosing between them in practice - Use `never()` when other traffic to the same server is expected and legitimate, which is the common case in an integration test that exercises several endpoints. - Use `verifyZeroInteractions()` when the whole point of the test is that no call was made — a cache hit, a short-circuit on invalid input, a disabled feature. - Use `never()` with a deliberately broad definition when "nothing on this endpoint" is the claim but other endpoints are in play. ## Pitfalls - Reading a green `never()` as evidence the server was untouched. That is `verifyZeroInteractions()`'s claim, not `never()`'s. - Passing a request definition to `verifyZeroInteractions()`. It takes none; if you need scope, you need `never()`. - Writing `atMost(0)` where `never()` reads better. They assert the same thing, and the named factory states the intent for the next reader. - Letting a negative assertion survive a refactor that renamed the endpoint. Nothing will tell you; the assertion simply stops meaning anything.
- Can verifyZeroInteractions be narrowed to one endpoint of the catalogue API?No. It takes no request definition and asserts that the whole journal is empty, so a request to any path fails it. When you need "nothing hit the holds endpoint but reads are fine", the tool is `verify` with a definition and `VerificationTimes.never()`.
- How do you keep a never() assertion from passing because its path was mistyped?Build the request definition once, in a helper, and use the same object in a positive test that expects the call to happen. If the path is wrong that positive test fails immediately and loudly, which is the only signal a negative assertion can never give you on its own.
- Does a passing verifyZeroInteractions prove the code under test did nothing?It proves that nothing reached this MockServer instance. Traffic to another host, another port or an in-process cache is invisible to it, so it is evidence about one dependency's socket rather than about the code path as a whole.
saying these in an interview costs you the question
- Treats never() as proof the server was untouched
- Trusts a negative assertion whose definition was never exercised positively
- Thinks verifyZeroInteractions takes a request definition
- Uses never() on a path the client never touches anyway
- Assumes a passing never() means the client made no call