In WireMock, when does matchingJsonPath() beat equalToJson() for a large peatland survey body?
answer
- open assertion versus closed assertion
- matches when the expression selects something
- second argument constrains the selected value
- filter expressions narrow an array
- several patterns are combined with AND
basics
~20 sIn WireMock, matchingJsonPath selects part of a body with a JSONPath expression and matches when the expression returns something. The rest of the payload may vary freely. Use it when one field decides which canned response the stub returns.
solid answer
~50 sWireMock's `equalToJson(` asks a question about the whole document; WireMock's `matchingJsonPath(` asks one about a part of it. Give it an expression such as `$.readings[0].plotId` and the pattern matches when the expression selects at least one node, whatever else the peatland carbon-survey body contains. WireMock's two-argument form adds a value constraint - `matchingJsonPath("$.siteId", equalTo("PEAT-114"))` matches one site only - and a filter expression such as `$.readings[?(@.waterTableCm < -20)]` lets the stub fire only for drained plots. Because WireMock keeps every pattern passed to `withRequestBody(` and requires all of them to match, two or three narrow paths build a precise predicate without a brittle expected document. A body that does not parse as JSON selects nothing, so the pattern simply fails. It wins whenever the payload is large, partly generated, or full of fields the stub genuinely does not care about.
code
java · 6 linesimport static com.github.tomakehurst.wiremock.client.WireMock.*;
stubFor(post(urlPathEqualTo("/carbon/v1/surveys"))
.withRequestBody(matchingJsonPath("$.siteId", equalTo("PEAT-114")))
.withRequestBody(matchingJsonPath("$.readings[?(@.waterTableCm < -20)]"))
.willReturn(aResponse().withStatus(202)));go deeper
Know that WireMock's matchingJsonPath takes a JSONPath expression and matches when that expression finds something in the request body, unlike equalToJson which looks at the whole document.
Explain the two overloads, why an empty selection is a miss, and that WireMock requires every pattern passed to withRequestBody to match rather than any one of them.
Show the tradeoff between an open and a closed assertion on a real payload, and be ready to work a path predicate that never fires down to its actual cause rather than rewriting the expression.
Own when a team is allowed to pin a whole payload at all, and how mappings stay readable when the selecting condition is expressed as a path predicate rather than buried in an expected document.
## Two shapes of assertion A body matcher is an assertion, and WireMock offers it in two shapes. WireMock's `equalToJson(` is a **closed** assertion: it describes the entire document, so anything you did not describe is by implication forbidden. WireMock's `matchingJsonPath(` is an **open** assertion: it describes one place in the document and says nothing about the rest. On a small fixed payload the closed form is fine. On a large one it is a liability, because you end up committing the stub to fields that have nothing to do with why the stub exists. The peatland carbon-survey submission at `POST /carbon/v1/surveys` is the large case. A real submission from a field device carries site identifiers, a `readings` array with one entry per plot, instrument metadata, a client version and a timestamp. Perhaps two of those fields decide which response the stub should return. ## What WireMock's `matchingJsonPath(` actually evaluates WireMock parses the body, evaluates the JSONPath expression against it, and matches when the result is a non-empty selection: - In WireMock, `matchingJsonPath("$.siteId")` matches any submission that carries a `siteId` at all. - In WireMock, `matchingJsonPath("$.readings[0].plotId")` matches when the first reading has a `plotId`, and says nothing about the readings after it. - In WireMock, `matchingJsonPath("$.readings[?(@.waterTableCm < -20)]")` uses a filter, so the stub fires only when at least one plot is drained below twenty centimetres. - WireMock's two-argument form, `matchingJsonPath("$.siteId", equalTo("PEAT-114"))`, constrains the value of the selected node rather than merely its existence. - A body that does not parse as JSON selects nothing, so the pattern fails; there is no text fallback. The value-pattern overload is the part people forget, and it is what makes the matcher genuinely competitive with whole-document matching. Without it you can only say "this field is present"; with it you can say "this field is present and equals this", which is usually the assertion you meant. ## Composing several paths WireMock keeps every pattern handed to `withRequestBody(` in a list and requires **all** of them to match. That turns narrow path predicates into building blocks: ```java stubFor(post(urlPathEqualTo("/carbon/v1/surveys")) .withRequestBody(matchingJsonPath("$.siteId", equalTo("PEAT-114"))) .withRequestBody(matchingJsonPath("$.readings[?(@.waterTableCm < -20)]")) .willReturn(aResponse().withStatus(202))); ``` Read that mapping cold and you know exactly what the stub is for: this site, at least one drained plot. The equivalent written as a relaxed WireMock `equalToJson(` would be a block of expected document in which those two conditions are invisible among a dozen fields that are there only because the recorder captured them. ## When the whole-document form still wins Open assertions are not free. WireMock's `matchingJsonPath(` will happily match a body that has gained a field with real meaning, because it never looked. Prefer the closed form when: 1. The payload is small and stable, and its shape is genuinely what the test is about. 2. You are standing in for an endpoint whose validation rules you want the stub to mirror. 3. A reviewer needs to see the exact request the client is supposed to send, in one place. Outside those cases the open form is usually the honest one, because a stub that pins fields nobody reads is making an assertion nobody chose. ## The same idea elsewhere MockServer expresses the selective JSON matcher as `jsonPath(` on the request body, which is the same idea under a different spelling - a stub author moving between the two products has to change the name, not the thinking. ## Diagnosing a path predicate that never matches When a WireMock `matchingJsonPath(` stub does not fire, the cause is almost always one of a short list: - The expression is valid but selects nothing, for example an index past the end of `readings`. - The body is not JSON at all, so nothing can be selected. - A filter expression compares a string to a number, so it silently selects nothing. - The value pattern is stricter than intended, for instance an exact match where the client sends a trailing space. - The path is right but a second body pattern on the same stub is the one failing, because every pattern must match. Working down that list beats rewriting the expression at random, and the last item is the one that catches experienced people out: adding a path predicate to an existing stub tightens it, and the newly failing condition may be the pattern that was already there. ## The judgment to carry away Choose the matcher that says what the stub is for and nothing more. On a peatland carbon-survey payload with twenty fields, WireMock's `matchingJsonPath(` with a value pattern usually expresses the intent in one line, survives every additive change the client makes, and leaves a mapping the next reader can understand without diffing two JSON documents in their head.
- Your WireMock matchingJsonPath filter compares waterTableCm to a number but never matches. What do you check?Whether the client is sending the value as a string. In JSON, `"-12"` and `-12` are different, and a filter expression comparing a string to a number selects nothing rather than failing loudly. Confirm the type in the actual payload, then either fix the client or match on the string form deliberately.
- How does adding a second matchingJsonPath pattern change an existing WireMock stub?It tightens it. WireMock keeps every pattern given to `withRequestBody(` and requires all of them to match, so the stub now fires on strictly fewer requests than before. If it stops matching after the addition, the failing pattern may be either the new one or the one that was already there.
It is the difference between checking one name against a guest list and comparing the whole guest list line by line. A JSONPath predicate reads the one field that decides the outcome, while whole-document equality insists everything else agrees too.
saying these in an interview costs you the question
- Thinks matchingJsonPath is evaluated against the raw body text
- Forgets the overload that constrains the selected value
- Believes a second withRequestBody call replaces the first
- Expects an empty selection to count as a match
- Uses a whole-document matcher on a large generated payload