skip to content

In WireMock, what does a catch-all stub that answers every request hide in a ferry-timetable suite?

level: middleimportance: should knowfreq 52%

answer

  1. one mapping answering for all
  2. nothing can fail to match
  3. error branches never execute
  4. the specific stubs stop being served

basics

~20 s

A WireMock catch-all stub answers every request, even ones nobody stubbed. A wrong path or an unstubbed ferry endpoint gets the same canned 200, so the client's error branches never run. The specific mappings beneath it stop being served.

solid answer

~50 s

A catch-all is a WireMock stub broad enough to match anything — typically `any(anyUrl())` returning a bland `200` — added because one test kept failing, and then left in the shared fixture. From that moment the suite stops distinguishing a request the fixture was designed for from one it was not: a typo in `/v1/routes/DOV-CAL/departures`, a missing `date` query parameter and a call to an endpoint nobody ever stubbed all come back `200` with the same body. The client's 404 and 503 branches are never exercised, and the specific ferry mappings underneath are no longer the ones serving, so a renamed upstream path stays green. None of this is a WireMock defect — the server did exactly what the mapping said. The defect is leaving the mapping in place, because it converts every future mismatch into a pass.

code

java · 14 lines
java
// Ferry timetable fixture, WireMock Java DSL
stubFor(get(urlPathEqualTo("/v1/routes/DOV-CAL/departures"))
    .withQueryParam("date", matching("[0-9]{4}-[0-9]{2}-[0-9]{2}"))
    .willReturn(aResponse()
        .withStatus(200)
        .withHeader("Content-Type", "application/json")
        .withBodyFile("ferry/departures-dov-cal.json")));

// Added to unblock a red build, then never removed:
// every request the fixture was not designed for now succeeds
stubFor(any(anyUrl())
    .willReturn(aResponse()
        .withStatus(200)
        .withBody("{}")));

go deeper

for a junior

Be able to say what catch-all means in a WireMock fixture: one mapping broad enough to answer any request, so nothing the client sends can fail to match and nothing can be reported as missing.

for a middle

Explain what disappears mechanically — the client's 404 and 503 branches, and the specific ferry mappings that are no longer the ones serving — and why the suite nevertheless stays green.

for a senior

Show how you would prove the shadowing rather than argue about it: narrow or remove the mapping in a scratch run, read which tests turn red, and fix the ones that were only ever passing because of it.

for a principal

Own the standard for shared fixtures: whether a broad mapping is ever allowed, who may add one and what evidence must accompany it, because a single catch-all weakens every suite that loads that set.

## What a catch-all mapping is In WireMock a stub mapping is a pair: a request pattern and a response definition. Most mappings are narrow — one method, one path, sometimes a query parameter or a body constraint — so the pattern is a statement about which requests this fixture was designed for. A **catch-all** is a mapping whose pattern constrains nothing that matters: `any(anyUrl())` in the Java DSL, or a mapping file with neither a method nor a URL constraint, paired with a bland success such as `aResponse().withStatus(200).withBody("{}")`. Nobody sets out to write one. It appears at a specific moment: a build is red, a client is calling a path the fixture never stubbed, a release is waiting, and one mapping that answers everything makes the red go away in a single line. In Mountebank the same shape is a stub with an empty `predicates` array, which matches every request reaching that imposter. ## What it absorbs Once it is registered, these all become indistinguishable from a correct call: - A **typo in the path** — `/v1/routes/DOV-CAL/departues` instead of `/departures`. - A **wrong method** — a `POST` where the ferry client should send `GET`. - A **missing or malformed parameter** — no `date` on the departures call, or a date the real service would reject. - An **endpoint nobody stubbed** — `POST /v1/bookings` added to the client this sprint and never added to the fixture. - A **call that should not happen at all** — a duplicate booking submission, or a departure lookup fired once per row in a loop. Every one of those gets `200` and the same body, so the client proceeds down its happy path and the test asserts happily on the result. ## What stops being exercised | Request the ferry client makes | Fixture without the catch-all | Fixture with a mapping answering everything | |---|---|---| | `GET /v1/routes/DOV-CAL/departures?date=…` | served by the specific mapping | served by whichever mapping matched; the test cannot tell which | | the same path with a typo | no mapping matches and WireMock replies with its not-found response | `200` with the canned body | | `POST /v1/bookings`, not yet stubbed | no mapping matches | `200`, and the booking code proceeds as if it worked | Two consequences follow. First, the **client's error handling is dead code** for the duration: the branch that reads a 404 for an unknown departure identifier and the branch that backs off on a 503 are never entered, so a bug in either ships unnoticed. Second, the **specific mappings underneath stop being served**. WireMock's `GET /__admin/mappings/unmatched` reports the stubs that were never served, which is the mechanical face of the shadowing — the ferry mappings the fixture was built around show up there while the broad one quietly answers in their place. ## Why the suite still passes A test asserts on what the client produced from the response, not on which mapping produced it. A canned `200` with a well-formed body satisfies that assertion by construction. The suite is therefore measuring the fixture's willingness to reply, not the client's correctness. This is the sense in which a catch-all *hides* things rather than *breaks* them: nothing is red, the coverage report is unchanged, and the only visible symptom is the absence of failure where failure would have been informative. The judgement worth stating out loud in an interview is that the server behaved correctly. WireMock matched the request against a registered mapping and returned the response that mapping defined. The defect is entirely in the decision to leave the mapping in the fixture, because a mapping that can answer anything converts every future mismatch into a pass — including mismatches that do not exist yet, such as an upstream path renamed a year from now. ## What to do instead 1. **Scope it.** If a broad mapping is genuinely needed to unblock work, constrain it to the one path prefix that needed it, so it cannot answer for the rest of the API. 2. **Make it loud.** A temporary mapping that returns a failure status and a body naming the path it caught attributes an unstubbed ferry call instead of absorbing it. 3. **Time-box it.** Attach metadata naming an owner and a review date, so the mapping is a visible exception rather than a permanent part of the set. 4. **Remove it and read the reds.** Each newly failing test names a real gap: a missing mapping, a wrong URL in the client, or a call that should never have been made. The last step is the one that pays. A catch-all is cheap to add and cheap to remove; what is expensive is the interval in between, during which the suite reports success it has not earned.

  • Is there ever a legitimate use for a WireMock mapping that matches everything?
    Yes, when the breadth is the point and the reply is loud rather than bland — a temporary mapping that returns a failure status and echoes the path it caught attributes an unstubbed ferry call instead of swallowing it. What is not legitimate is a permanent `200` with an empty body in a shared fixture, because that turns every future mismatch into a pass for every suite that loads the set.
  • Why does removing a long-lived catch-all usually turn several unrelated tests red?
    Because those tests were never exercising the mapping they claim to. Once the broad mapping is gone, requests that only ever matched it stop being answered, and each red names one real gap: a path the fixture never stubbed, a parameter the client actually sends, or a call the code should not be making. Fix them one at a time rather than restoring the mapping to recover green.

saying these in an interview costs you the question

  • Assumes the broad mapping is harmless because it only answers leftovers
  • Treats a green suite as evidence the client sent the right request
  • Says the fix is to give the catch-all a richer response body
  • Believes an unstubbed endpoint would still fail somewhere later anyway
  • Keeps the catch-all and silences verification to stop the noise