skip to content

In a Postman test script, how do the `body` and `jsonBody` matchers differ in what they read from the reply?

level: middleimportance: must knowfreq 60%

answer

  1. One reads text, one parses first
  2. A regular expression only fits the text one
  3. The parsed one takes a path and a value
  4. Its two-argument form is a containment check
  5. A subset passes; extra fields go unnoticed

basics

~20 s

Postman's body matcher reads the reply as raw text and takes a string or a regular expression; jsonBody parses the reply first and then takes a path, an expected value, or an object. Both hang off pm.response.to.have.

solid answer

~40 s

`body` works on the reply's **text**. Called bare, `pm.response.to.have.body()` asserts there is content at all; with a string it asserts the text equals it; with a RegExp it asserts the text matches, and the failure quotes the body text. `jsonBody` **parses first**, so `pm.response.to.have.jsonBody()` fails when the payload is not valid JSON, reporting the parse error. Given a path — `jsonBody('data.id')` — it asserts that property exists in the parsed payload; given a path and a value — `jsonBody('data.id', 7)` — it asserts the value at that path **contains** the expected one, which for an object argument is a subset check rather than deep equality. A single object argument, `jsonBody({ authenticated: true })`, applies that containment to the whole parsed payload.

code

javascript · 7 lines
javascript
pm.response.to.have.body();
pm.response.to.have.body('pong');
pm.response.to.have.body(/^pong$/);
pm.response.to.have.jsonBody();
pm.response.to.have.jsonBody('data.id');
pm.response.to.have.jsonBody('data.id', 7);
pm.response.to.have.jsonBody({ authenticated: true });

go deeper

for a junior

Recall which matcher to reach for: body when the reply is text, jsonBody when it is JSON. Know that jsonBody with no argument just checks the payload parses.

for a middle

Explain each arity of both matchers, that jsonBody parses before comparing, and that its two-argument form checks containment at a path rather than deep equality across the whole payload.

for a senior

Demonstrate judgment about brittleness: text comparison of a JSON payload breaks on reformatting, and containment silently tolerates new fields. Say which risk a given suite should accept and why.

for a principal

Own the house rule on payload assertions across many collections: how much of a payload a test may pin, whether unexpected fields must ever fail a build, and what that choice costs when the API evolves.

## Two links, two readings of the same bytes A reply arrives as a sequence of bytes. Postman's assertion chain offers two ways to look at those bytes, and choosing the wrong one is the most common cause of an assertion that is either useless or unexplainably red. Both hang off `pm.response.to.have`: - **`body`** treats the reply as **text** and never parses it; - **`jsonBody`** **parses** the reply as JSON first and then works with the resulting object. That is the whole distinction, and every difference in behaviour and failure message falls out of it. ## What `body` does `body` has three shapes: | Call | Assertion | What a failure says | |---|---|---| | `body()` | there is content in the body | that the response has (or has not) content in body | | `body('pong')` | the text equals that string | that the body does (or does not) equal that string | | `body(/pong/)` | the text matches that pattern | quotes the body text and the pattern it did not match | Because it never parses, `body('{"ok":true}')` is a **whitespace-sensitive, key-order-sensitive string comparison**. It will fail when the server reformats its output even though nothing about the API changed. That is exactly the brittleness `jsonBody` exists to avoid, and a candidate who reaches for `body` on a JSON API is signalling they have not made that distinction. `body` is the right tool when the payload genuinely is text — a health probe that answers `pong`, a plain-text error, a fragment you want to spot inside HTML with a regular expression. ## What `jsonBody` does `jsonBody` parses before it compares, and its arity decides what it compares: 1. **`jsonBody()`** — asserts only that the body parses. On a body of `undefined` it fails with a message naming the parse error, and negated on a valid payload it fails with the complaint that the body is valid JSON after all. 2. **`jsonBody('prop[0]')`** — asserts the parsed payload **contains that property path**. The path syntax accepts dots and bracketed indexes, so `data.items[0].id` is one path. 3. **`jsonBody('prop[0].value', { v: 1 })`** — drills to the path, then asserts the value there **contains** the expected value. The failure reads `expected response body json at "prop[0].value" to contain { v: 1 } but got { oh: 'wow' }`. 4. **`jsonBody({ authenticated: true })`** — no path, so containment is applied to the whole parsed payload. ## Containment, not equality The word to hold on to is **contain**. For a scalar the distinction is invisible: `jsonBody('data.id', 7)` and an equality check behave the same. For an object it matters a great deal. `jsonBody({ authenticated: true })` passes against a payload that also carries a dozen other fields, because it is asserting a **subset**. That is usually what you want from an API test — it survives the server adding a field — but it means the matcher will **never** tell you that an unexpected field appeared. If a test's real job is "exactly these fields and no others", containment is the wrong instrument and you need to say so explicitly rather than assuming `jsonBody` covers it. ## Choosing between them - Reply is JSON and you care about one field → `jsonBody(path, value)`. - Reply is JSON and you only care that it parses → `jsonBody()`. - Reply is JSON and you care about several fields at once → `jsonBody(object)` for the subset. - Reply is text, or you want a pattern rather than a value → `body(string)` or `body(/pattern/)`. - You only care that anything came back → `body()`. ## Reading the failures The two matchers produce visibly different messages, and that is a diagnostic in itself. If a failure quotes raw body text, you were on the `body` link and the comparison was textual. If it names a **path** and shows the value found there, you were on `jsonBody` and the payload parsed fine — the shape simply did not match. If it names a **parse error**, the payload was not JSON at all, which usually means the server answered with an error page, a proxy interposed, or a content type you did not expect. That last case is worth recognising quickly, because no amount of fixing the expected value will help until the reply is JSON again.

  • Why does `jsonBody({ authenticated: true })` still pass when the payload carries ten other fields?
    Because the comparison is containment, not deep equality — the expected object is treated as a subset of what came back. That makes the assertion survive the server adding fields, but it also means the matcher can never report an unexpected field. If exact shape is the requirement, containment is the wrong instrument.
  • A test asserting a JSON payload with `body('{"ok":true}')` broke when the server reformatted its output. What went wrong?
    `body` compares raw text, so whitespace, indentation and key order all count. Reformatting changed the bytes without changing the API. Use `jsonBody({ ok: true })`, which parses first and compares structure, so formatting differences stop mattering.

saying these in an interview costs you the question

  • Compares a JSON payload as a raw string with body
  • Expects jsonBody to enforce exact, whole-payload equality
  • Passes a regular expression to jsonBody
  • Thinks body parses the payload before comparing
  • Assumes a passing subset check rules out extra fields