skip to content

How would you set a WireMock body-matching strictness policy across many peatland survey stub sets?

level: principalimportance: should knowfreq 46%

answer

  1. default plus a written exception list
  2. tolerant to additions, strict about omissions
  3. array order decided per endpoint
  4. selecting fields get their own path predicate
  5. enforced in review, not by a setting

basics

~20 s

Default to WireMock's equalToJson with ignoreExtraElements on, so stubs survive additive payload changes, and use matchingJsonPath for the one or two fields that actually select the response. Reserve strict whole-body equality for the few endpoints whose entire payload genuinely matters.

solid answer

~50 s

A policy here is a default plus a short list of exceptions. My default for a peatland carbon-survey estate is WireMock's `equalToJson(` with `ignoreExtraElements` on and `ignoreArrayOrder` decided per endpoint: on where the API's own contract says order is insignificant, off where it is not. On top of that, any field that actually *selects* a response gets its own WireMock `matchingJsonPath(` predicate, so the intent of a stub is legible in the mapping instead of buried in a hundred-line expected document. WireMock's `containing(` is allowed only for opaque or non-JSON payloads, and fully strict `equalToJson(` only where a reviewer signs off that the whole payload is the point. Write the rule into the mapping-file conventions and enforce it in review; what it buys is a stub set nobody has to re-edit on every additive change.

code

json · 18 lines
json
{
  "request": {
    "method": "POST",
    "urlPath": "/carbon/v1/surveys",
    "bodyPatterns": [
      {
        "equalToJson": {
          "siteId": "PEAT-114",
          "readings": [ { "plotId": "PLOT-7", "waterTableCm": -12 } ]
        },
        "ignoreExtraElements": true,
        "ignoreArrayOrder": true
      },
      { "matchingJsonPath": "$.readings[?(@.waterTableCm < 0)]" }
    ]
  },
  "response": { "status": 202 }
}

go deeper

for a junior

Know that WireMock offers several body matchers of different tightness and that a team usually picks a default. Be able to name equalToJson, matchingJsonPath and containing and say roughly how strict each is.

for a middle

Explain what each matcher asserts and why ignoreExtraElements is the asymmetric one, forgiving added content while still failing on a field the client stopped sending.

for a senior

Show that you would choose per endpoint rather than globally, and be ready to justify why an over-tight matcher costs a team more in edits than it ever catches in defects.

for a principal

Own the default, the exception process and the review question that enforces both, and be explicit about what the tolerant default gives up and why that trade is the right one for a stub estate.

## What a strictness policy is actually deciding Every body matcher is an assertion the stub makes about the request. Set it too loose and the stub answers requests it should never have answered; set it too tight and the stub set becomes a second schema that somebody has to maintain by hand. A policy is the place you decide, once, where the default sits and who may depart from it. On a WireMock estate the decision is concrete, because WireMock gives you exactly four instruments for a body and they sit at different points on that scale. | Instrument | What it asserts | Where it belongs in a policy | |---|---|---| | WireMock `equalToJson(` with no flags | the whole document corresponds | exception only, with a named reviewer | | WireMock `equalToJson(` with `ignoreExtraElements` | your document is contained in what arrived | the house default | | WireMock `matchingJsonPath(` | one selected node exists, or has a given value | for every field that selects the response | | WireMock `containing(` | a substring is present in the raw body | opaque or non-JSON payloads only | MockServer's `subString(` occupies the same slot as WireMock's `containing(` for teams that run both. Mountebank writes all of this with one of its eleven predicates - `equals`, `deepEquals`, `contains`, `startsWith`, `endsWith`, `matches`, `exists`, `not`, `or`, `and`, `inject` - applied to the request's `body` field. ## The default, and why it is that one The house default is WireMock's `equalToJson(` with `ignoreExtraElements` on. The reason is the asymmetry of that flag: it forgives content the client added and still fails when the client stops sending something the expected document declares. That is precisely the direction a stub set should be tolerant in. Additive change upstream is routine and harmless to a stub; a field disappearing is not, and you want that to surface as a failure rather than be absorbed. WireMock's `ignoreArrayOrder` gets the opposite treatment - it is decided per endpoint, not globally: - On, where the peatland carbon-survey API documents `readings` as an unordered set. - Off, where order carries meaning, because switching it on globally hides a genuine ordering bug in the client for as long as the stub exists. ## Making intent legible The second half of the policy is about readability, and it is the half that decays first. A stub whose expected document is ninety lines long tells a reviewer nothing about which two fields the stub actually cares about. So the rule is: **whatever selects the response gets its own WireMock `matchingJsonPath(` predicate**, even when the whole-document matcher would already have covered it. WireMock keeps every pattern passed to `withRequestBody(` and requires all of them to match, so adding the explicit path costs nothing at runtime and buys a mapping a newcomer can read. 1. Name the endpoint and the one thing that distinguishes this stub from its neighbours. 2. Write that as a WireMock `matchingJsonPath(` predicate, with a value pattern when the value matters. 3. Add the tolerant WireMock `equalToJson(` only if the rest of the document is genuinely part of the assertion. 4. If you reach for WireMock's `containing(`, write a one-line comment in the mapping's metadata saying why the payload cannot be parsed. ## Enforcing it without a linter There is no WireMock setting that enforces a strictness policy, so enforcement is social and it has to be cheap: - Put the four-row table above in the repository's stub conventions, next to the mapping files. - Make "is this the loosest matcher that still expresses the intent?" a review question, in both directions - reviewers should challenge an over-tight matcher as readily as an over-loose one. - Ship a small set of exemplar mappings that new stubs are copied from; defaults propagate by copy-paste far more reliably than by documentation. - Treat a stub that had to be edited twice in a quarter for reasons unrelated to its purpose as evidence the matcher is too tight, and loosen it deliberately rather than by attrition. ## The tradeoffs to say out loud A principal-level answer names what the policy costs, not only what it buys: - A tolerant default means a stub can match a request that has quietly gained a field with real meaning. You accept that in exchange for a stub set that does not need touching on every release. - Requiring an explicit WireMock `matchingJsonPath(` predicate adds a line to every mapping. You accept that in exchange for mappings a reviewer can read at a glance. - Allowing strict whole-document matching anywhere means somebody will copy it somewhere it does not belong, which is why the exception needs a named owner rather than a general permission. - Two products in one estate double the vocabulary. If teams run both WireMock and MockServer, the policy has to state the equivalence explicitly, because the two products' JSON defaults are opposite and a habit carried across is a silent behaviour change. The test of the policy is not whether it is elegant but whether a new engineer, copying the nearest existing mapping, lands on the right matcher by accident.

  • How would you handle one endpoint where the team insists the whole payload must be pinned?
    Allow it as a named exception with an owner. Use WireMock's `equalToJson(` with no flags, keep the expected document in the mapping file rather than in code, and require that the reviewer who approved it is recorded. The point of the exception process is that the cost lands on the team asking for it, not on everyone maintaining the estate.
  • Your teams run both WireMock and MockServer. What does the policy have to say explicitly?
    That the two products' JSON defaults are opposite: WireMock's `equalToJson(` is strict until you relax it, while MockServer's `json(` starts at `MatchType.ONLY_MATCHING_FIELDS` and is tightened with `MatchType.STRICT`. Without that sentence written down, an engineer moving between the two carries a habit across and changes stub behaviour without noticing.
  • What signal tells you the policy is set too tight?
    Stubs being edited for reasons that have nothing to do with what they assert - a new optional field, a reordered array, a client library bump. Two such edits in a quarter on the same mapping is enough evidence to loosen it. The counter-signal is a stub answering a request whose shape has genuinely changed, which argues the other way.

saying these in an interview costs you the question

  • Prescribes strict whole-document matching everywhere as rigour
  • Turns ignoreArrayOrder on globally without asking the contract
  • Treats containing as a general-purpose JSON matcher
  • Assumes WireMock has a setting that enforces matcher strictness
  • Carries MockServer JSON habits into WireMock unchanged