skip to content

In MockServer, why retrieve recorded catalogue hold requests instead of asserting the whole body in verify?

level: seniorimportance: should knowfreq 50%

answer

  1. verify answers yes or no only
  2. a generated id cannot be predicted
  3. get the requests, do not match them
  4. retrieveRecordedRequests takes the same definition
  5. assert fields yourself, skip the correlation id

basics

~20 s

MockServer's verify answers only match or no match, so a whole-body pattern breaks on any generated field. retrieveRecordedRequests hands the received requests back as objects, so your own assertions can target the fields that matter and report which one differed.

solid answer

~40 s

`MockServerClient.verify` is a binary instrument: the request definition you pass either matches something in the journal or it does not. That is exactly right for asserting arrival and count, and exactly wrong for asserting content, because the moment the hold body carries a client-generated correlation id or a timestamp the pattern has to encode a value the test cannot predict — and the failure message still tells you only that nothing matched. `MockServerClient.retrieveRecordedRequests(request().withPath("/catalogue/v1/holds"))` takes the same kind of definition but **returns** the matching requests instead of asserting on them. You then parse each body with your own JSON library and assert field by field — `isbn` equals the title you asked for, `branchCode` is `riverside` — while ignoring the correlation id entirely. WireMock's retrieval equivalents are `findAll(` and `getAllServeEvents`.

code

java · 12 lines
java
import static org.mockserver.model.HttpRequest.request;
import org.mockserver.model.HttpRequest;

HttpRequest[] holds = mockServerClient.retrieveRecordedRequests(
    request().withMethod("POST").withPath("/catalogue/v1/holds"));

assertThat(holds).hasSize(1);
JsonNode body = objectMapper.readTree(holds[0].getBodyAsString());

// correlationId is generated per call and is deliberately not asserted
assertThat(body.get("isbn").asText()).isEqualTo("9780571356485");
assertThat(body.get("branchCode").asText()).isEqualTo("riverside");

go deeper

for a junior

Know that MockServer can hand the received requests back to the test rather than only answering whether one matched, and that retrieveRecordedRequests is the call that does it.

for a middle

Explain that the same request definition selects in both calls, but one asserts inside the server while the other returns HttpRequest objects your test must then assert on itself.

for a senior

Argue the trade honestly: pin arrival and count with verify, pull content back with retrieveRecordedRequests, and never encode an unpredictable value into a pattern whose only failure message is no match.

for a principal

Decide how much of an outbound request a suite is allowed to pin, so contract-relevant fields are asserted somewhere while incidental ones stay free to change without a mass test edit.

## Two instruments on the same journal MockServer exposes the request journal twice. `MockServerClient.verify` is an **assertion**: you hand it a request definition, and it either finds a match and returns or finds nothing and throws. `MockServerClient.retrieveRecordedRequests` is a **query**: you hand it the same kind of definition and it returns the matching entries as `HttpRequest` objects for your test to inspect. The difference sounds cosmetic and is not, because it decides where the assertion logic lives and what a failure is able to tell you. ## Where a verification pattern runs out Consider the hold the library client places: `POST /catalogue/v1/holds` with a body of `isbn`, `branchCode`, `patronId`, a client-generated `correlationId` and a timestamp. Asserting that body inside `verify` means writing a pattern that reproduces it — and the pattern has to name values for the correlation id and the timestamp that the test cannot know in advance. Three unattractive options follow: - Pin the unpredictable fields to literals, and the test fails on every run. - Leave them out and lean on the matcher's leniency, which quietly stops asserting the fields you *did* care about when the shape shifts. - Loosen the body pattern until it asserts almost nothing, which is the option teams actually take. Even where a pattern is achievable, its failure message is binary. MockServer reports that nothing matched and shows you the journal; it does not report that `branchCode` was `eastgate` when you expected `riverside`. On a five-field body, "no match" is the start of a diff, not a diagnosis. ## What retrieveRecordedRequests gives back `retrieveRecordedRequests(request().withMethod("POST").withPath("/catalogue/v1/holds"))` returns an `HttpRequest[]` of the journal entries that matched. From there the test is ordinary code: read the body as a string, parse it with whatever JSON library the project already uses, and assert field by field with the assertion library the rest of the suite uses. The correlation id is simply never mentioned, and when `branchCode` is wrong the failure names the field and both values. | | `verify(definition, times)` | `retrieveRecordedRequests(definition)` | |---|---|---| | what it does | asserts inside the server | returns matching requests to the test | | result on failure | `AssertionError` from MockServer | whatever your assertion library raises | | best at | arrival and exact count | the value of a specific field | | trap | brittle on unpredictable values | an empty array asserts nothing at all | ## The trap on the retrieval side `retrieveRecordedRequests` does not fail. When nothing matched it returns an empty array, and a test written as "loop over the results and assert inside the loop" passes vacuously — zero iterations, zero assertions, green. Two habits prevent it: 1. Assert the array's length before touching any element, so an empty result is a failure rather than a no-op. 2. Keep a `verify` alongside it for arrival and count, and let the retrieval carry only the content assertions. The second habit is worth the duplication. The two calls answer different questions, and a test that does both reports "the hold went out twice" and "the hold carried the wrong branch" as two distinct failures instead of one confusing one. ## Choosing between them - Arrival and count belong in `verify`. They are cheap to write, cheap to read in a failure, and a payload change never touches them. - Content belongs in `retrieveRecordedRequests`, and only the fields that carry meaning for the behaviour under test. - A body with a single stable field is the one case where a body pattern inside `verify` is genuinely the shorter test. - Anything the client generates — correlation ids, timestamps, nonces — should never appear in a verification pattern at all. ## Pitfalls - Treating the retrieved array as an assertion. It is data; nothing fails until you assert on it. - Asserting on the raw body string with an equality check, which reintroduces exactly the brittleness you left `verify` to escape. - Retrieving with a definition broader than the one you verified with, so the array picks up entries from a neighbouring endpoint and the index you assert on is not the request you meant. - Forgetting that both calls read the same journal, so anything that empties it between the act and the assertion empties it for both. - Reaching for retrieval when a count would have done. A test that pulls the body back only to check that something arrived has paid the maintenance cost of a content assertion for the value of a count.

  • What does retrieveRecordedRequests return when nothing matched the definition?
    An empty array. Unlike `verify` it does not fail on its own, so a test that only iterates the result and asserts inside the loop passes vacuously when nothing arrived. Assert the array's size first, or keep a `verify` alongside it to pin arrival and count.
  • Could you keep everything in verify by loosening the body pattern instead?
    You can, and for one or two stable fields it is the shorter test. It stops paying once several fields matter, because a failure still reports only that nothing matched, and the pattern quietly grows into a second copy of the request contract that nobody remembers to update.

saying these in an interview costs you the question

  • Encodes a client-generated correlation id into the verification pattern
  • Thinks retrieveRecordedRequests asserts and fails on its own
  • Assumes an empty result array fails the test automatically
  • Believes verify reports which body field differed
  • Uses retrieveRecordedRequests where a simple count assertion would do