When should a MockServer suite assert with verify counts rather than retrieved request bodies?
answer
- two instruments, two maintenance bills
- traffic facts are cheap to keep
- every asserted field is a future edit
- counts everywhere, content where it matters
- one content assertion per outbound call
basics
~20 sUse MockServer verify counts for facts about traffic: the hold call happened once, the cancellation never did. Reach for retrieveRecordedRequests only where a field carries contract meaning, because every field you assert is one the catalogue can later change.
solid answer
~40 sTreat the two as different instruments with different maintenance costs. `verify` with a `VerificationTimes` count answers questions about *traffic*: the hold was placed, exactly once; the cancellation endpoint was never touched. Those facts are cheap to state, cheap to read in a failure, and almost never need editing when the catalogue's payload evolves. `retrieveRecordedRequests` answers questions about *content*, and every field you pull out and assert is a field a future contract change will break. So pin content only where the field is the point of the test — the `isbn` the reader chose really reaches the catalogue, the `branchCode` matches the branch they were browsing — and leave the rest unasserted. A workable rule: one content assertion per outbound call, in the test that owns that call's behaviour, and count assertions everywhere else.
go deeper
Know that MockServer offers both a count assertion and a way to read the recorded request back, and that the two answer different questions about the same journal entry.
Explain the maintenance difference: a count assertion survives a payload change untouched, while every field pulled from a recorded request is an edit waiting for the next contract revision.
Argue a concrete rule for a suite you own — where content assertions earn their keep, where counts suffice — and show what each choice does to the failure message a teammate reads later.
Own the trade across teams: too much content assertion turns every contract change into a mass test edit, too little leaves integration edges unprotected, and the balance point differs per dependency.
## Two instruments, two maintenance bills Both of MockServer's assertion routes read the same request journal, but they age very differently, and that — not expressiveness — is what should decide between them. A **count assertion** is `MockServerClient.verify` with a `VerificationTimes`: the hold was placed exactly once, the cancellation endpoint was never touched, the availability read happened at least once. It names a method, a path and a number. None of those change when the catalogue's payload evolves, so the assertion sits untouched through a schema revision it has no opinion about. A **content assertion** is `MockServerClient.retrieveRecordedRequests` followed by your own checks on the returned `HttpRequest` objects: this body carried an `isbn`, and it was the ISBN the reader selected. It is strictly more informative, and it is also a copy of part of the request contract held in a test file. When the field is renamed, every copy needs an edit. ## What each one actually buys | | count assertion | content assertion | |---|---|---| | proves | the call happened, this many times | the payload carried these values | | breaks when | the call site is removed or duplicated | any asserted field is renamed or reshaped | | failure message | MockServer's, listing the journal | your assertion library's, naming the field | | cost per contract change | none | one edit per test that asserts the field | ## The failure both extremes produce - **All counts, no content.** Every test proves that a request went out and none proves the right one did. A bug that sends the wrong ISBN, the wrong branch code or an empty body is invisible, because the stub answers the same way regardless of what it received. - **All content, everywhere.** A renamed field turns into a hundred-file diff, so the team loosens the assertions in bulk to stop the bleeding, and the suite ends up with less real coverage than a disciplined middle would have given it. ## A rule that survives contact with a real suite 1. Every outbound call that changes state gets a count assertion, in every test that exercises a path through it. 2. Exactly one test per outbound call owns its content, and asserts only the fields that carry meaning for the feature — the identifier the reader chose, the branch they were browsing, an idempotency key. 3. Generated values — correlation ids, timestamps, nonces — are never asserted, in any test. 4. Everything else in the body is left unasserted on purpose, and that decision is written down so the next person does not "improve" the test by adding the rest. ## Where content assertions genuinely earn their keep - The field *is* the behaviour: a catalogue search that must carry the branch the reader is standing in. - The field is easy to get wrong and impossible to see downstream, such as an idempotency key that must stay stable across a retry. - The staged reply does not depend on the request, so no assertion on your own code's output can distinguish a correct payload from a wrong one. - The value crosses a boundary you do not own, where a silent mistake becomes a support ticket rather than a test failure. ## Reading the two failures A count failure reads as "expected one matching request, found two", followed by the journal. It points at a call site — a retry, a duplicated dispatch, a loop that ran twice. A content failure reads as "expected riverside but was eastgate" and points at a value — a wiring mistake, a defaulted parameter, a stale variable. Choosing the instrument therefore also chooses the shape of the message a teammate reads months later, and that is a real part of the cost. ## Pitfalls - Assuming a content assertion also pins the count. It does not: a test that reads the first retrieved request never notices there were three. - Copying one content assertion across a dozen tests because it looked useful, and creating a dozen future edits. - Dropping count assertions on the grounds that content assertions are stronger. They answer different questions and neither substitutes for the other. - Leaving an outbound call with no assertion of any kind because neither felt worth the effort, which is the only option that is definitely wrong.
- What is the cost of asserting every recorded field on every outbound catalogue call?Each assertion is a copy of part of the request contract, so a single renamed field triggers an edit in every test that touched it. Teams respond by loosening the assertions wholesale, which leaves less coverage than a disciplined one-field-per-call rule would have produced.
- Where would you still assert content even though a count would pass?Wherever the field is the behaviour under test: the ISBN the reader selected reaching the catalogue, the branch code matching the branch they browsed, an idempotency key staying stable across a retry. A count proves a request went out; only content proves the right one did.
- Does dropping content assertions weaken a suite that already checks its own outputs?Less than it sounds, where the outputs genuinely depend on the request. It weakens badly where the staged reply does not depend on the request, since then nothing downstream can distinguish a correct payload from a wrong one and the content assertion was the only witness.
A turnstile counter tells you how many people came through the door; the cloakroom ticket stubs tell you what each of them was carrying. A MockServer verify count is the turnstile, and a retrieved request is the stub.
saying these in an interview costs you the question
- Asserts every field of every recorded request by default
- Drops count assertions because content assertions feel stronger
- Thinks a content assertion also proves the call count
- Copies the same body assertion into a dozen unrelated tests
- Leaves an outbound call with no assertion of any kind