skip to content

Body Value Matching

Writing a body expectation as a path plus a Hamcrest matcher, sharing one root path across several of them, and picking which of the two failure-description styles gets printed on a mismatch.

part ofREST Assuredoverview, primer and where to startread it →
on this pageshow

questions

5

In REST Assured, how does then().body(path, matcher) differ from then().body(matcher) with no path?

level: middleimportance: must knowfreq 66%

answer

  1. two overloads, only one parses
  2. no path means raw body string
  3. path form: alternating key, matcher pairs
  4. no-path form: more matchers, same body
  5. hasXPath is the one exception

basics

~20 s

REST Assured's body(path, matcher) evaluates the path against the parsed response body and hands the extracted value to the matcher. The no-path body(matcher) form applies the matcher to the whole raw body string instead. Both accept any Hamcrest matcher.

solid answer

~50 s

`io.restassured.response.ValidatableResponse` declares both, and they reach different machinery. `body(String path, Matcher, Object...)` runs the path through the JSON or XML path engine, extracts a value, and asserts the matcher against *that value* — so `body("headCount", equalTo(24))` compares an `Integer`, not text. `body(Matcher, Matcher...)` has no path, so nothing is parsed: REST Assured matches against `response.asString()`, the raw body as it arrived. That makes it right for a plain-text reply or a `containsString` smoke check, and a poor way to check JSON. The trailing varargs differ too: on the path form they are alternating *key, matcher* pairs, as in `body("lotId", equalTo("LOT-4417"), "breed", equalTo("Angus"))`; on the no-path form they are simply more matchers, every one applied to the same raw body. One exception: REST Assured detects a `hasXPath` matcher and feeds it a parsed DOM even with no path.

code

java · 15 lines
java
import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.*;

// path form: the matcher sees the value the path expression returned
when().get("/lots/LOT-4417")
.then()
    .body("lotId", equalTo("LOT-4417"),
          "breed", equalTo("Angus"),
          "headCount", equalTo(24),
          "currentBid.bidderId", startsWith("BID-"));

// no-path form: every matcher sees response.asString() untouched
when().get("/lots/LOT-4417/state")
.then()
    .body(containsString("SOLD"), not(containsString("PASSED_IN")));

go deeper

for a junior

Be ready to write one body("currentBid.amount", ...) expectation and say where the value being compared came from — the path pulled it out of the parsed body.

for a middle

Explain the mechanical split: the path form parses and asserts on the extracted value, the no-path form matches response.asString() untouched, and the trailing varargs mean different things on each.

for a senior

Show judgment about which form belongs in a real suite. Whole-body string matching breaks on whitespace, key order and additive schema changes, so keep it for text replies and smoke checks and say why in review.

for a principal

Own the convention: decide where whole-body matching is allowed at all, what reviewers should push back on, and how failure output quality feeds into whether a red CI run is diagnosable without a rerun.

`io.restassured.response.ValidatableResponse` declares seven `body(...)` overloads, but two of them carry the whole story: `body(String path, Matcher<?> matcher, Object... additionalKeyMatcherPairs)` and `body(Matcher<?> matcher, Matcher<?>... additionalMatchers)`. Inside a `then()` chain they read almost identically, and they reach completely different machinery. Everything below uses a cattle-auction bidding API whose `GET /lots/LOT-4417` returns: ```json { "lotId": "LOT-4417", "breed": "Angus", "headCount": 24, "reserveMet": true, "currentBid": { "amount": 1875, "bidderId": "BID-88" } } ``` ## The path form parses, then matches the extracted value When you pass a path, REST Assured records an internal body matcher whose *key* is your path string. At validation time it picks a path engine from the response content type — the JSON assertion for JSON, the XML assertion for XML — evaluates the expression against the parsed document, and calls the Hamcrest matcher against whatever came back. The matcher therefore never sees JSON text: - `body("breed", equalTo("Angus"))` compares a `String`. - `body("headCount", equalTo(24))` compares an `Integer`. - `body("reserveMet", is(true))` compares a `Boolean`. - `body("currentBid.bidderId", startsWith("BID-"))` compares the nested `String`. The matcher is an ordinary `org.hamcrest.Matcher`. REST Assured ships no comparison vocabulary of its own for values — it supplies the extracted value and lets Hamcrest decide. Anything from `org.hamcrest.Matchers` works here, and so do the few response-oriented matchers REST Assured adds in `io.restassured.matcher.RestAssuredMatchers`. ## The no-path form never parses anything `body(Matcher, Matcher...)` records a body matcher whose key is `null`. REST Assured reads that as *no path parsing required* and matches against `response.asString()` — the raw body, byte for byte as it arrived. So `body(equalTo("LOT-4417 SOLD"))` is a reasonable assertion against a `text/plain` confirmation, and `body(containsString("LOT-4417"))` is a legitimate smoke check. Writing one giant string equality against a JSON payload is not: it breaks on whitespace, on key order, and on any field the server adds later, and the failure prints two long strings side by side. ## The two forms compared | | `body(path, matcher)` | `body(matcher)` | |---|---|---| | Body parsed? | yes, by the JSON or XML path engine | no | | Matcher sees | the value the path returned | `response.asString()` | | Typical matcher | `equalTo(24)`, `hasItem(...)` | `containsString(...)`, `matchesXsd(...)` | | Trailing varargs | alternating key, matcher pairs | more matchers for the same body | | Fails when | the path is wrong or the value differs | any byte of the body differs | ## The trailing varargs are not the same varargs This is the detail interviewers actually probe, because both signatures end in a vararg and the meanings diverge: 1. On the path form, `additionalKeyMatcherPairs` is read as alternating key and matcher, so `body("lotId", equalTo("LOT-4417"), "breed", equalTo("Angus"), "headCount", equalTo(24))` registers three independent path expectations in a single call. 2. On the no-path form, `additionalMatchers` is just more matchers, and every one of them is applied to the same raw body string. Confusing them compiles cleanly — the path form's vararg is `Object...`, so it accepts anything — and then fails at runtime while REST Assured tries to pair the values up. ## `hasXPath` is the documented exception There is exactly one case where a matcher passed with no path still receives a parsed document. Before matching, REST Assured asks whether the matcher is an `org.hamcrest.xml.HasXPath`, or whether its description text mentions XPath — a nested matcher counts. If so, it builds a DOM with `DocumentBuilderFactory`, honouring `XmlConfig`'s namespace-aware flag and any features you set, and matches against the root element instead of the string. That is why `body(hasXPath("/lot/breed", containsString("Ang")))` works with no path argument. ## Practical guidance - Reach for the path form by default: its failure message names the path and prints the actual value, which is what you want in a CI log. - Reserve the no-path form for non-structured bodies, for `containsString` smoke checks, and for the schema and DTD matchers designed to take a whole document. - Never build a whole-payload assertion out of one string equality; it is the most brittle expectation an API suite can carry. - Remember that both forms are seams onto Hamcrest — the expressiveness comes from the matcher you pass, not from REST Assured. - Watch the numeric type the matcher expects: an `Integer` value of 24 does not satisfy `equalTo(24L)`, and the failure reads `Expected: <24L>` over `Actual: <24>`. The short version an interviewer wants back: a path means *parse and extract, then match the value*; no path means *match the raw text*; and the varargs at the end change meaning between the two.

  • What does REST Assured do with the trailing arguments of body("lotId", equalTo("LOT-4417"), "breed", equalTo("Angus"))?
    It reads them as alternating key and matcher and registers each pair as its own independent path expectation, exactly as if you had written a separate `body(...)` call for each. The root path, if one is set, is merged onto every key in the list, not only the first.
  • Why can then().body(hasXPath("/lot/breed")) work when you give it no path string?
    REST Assured inspects the matcher before matching. If it is an `org.hamcrest.xml.HasXPath`, or its description text mentions XPath, REST Assured builds a DOM from the body with `DocumentBuilderFactory` under `XmlConfig` and matches against the root element rather than against the raw string.
  • When is matching the whole body as a string genuinely the right call?
    When the body is not structured data: a `text/plain` confirmation, an identifier echoed back, or a smoke check that some token appears at all. It is also how the document-level matchers such as `matchesXsd` and `matchesDtd` are meant to be passed, since they want the entire payload.

saying these in an interview costs you the question

  • Thinks body(equalTo(...)) with no path compares parsed JSON fields
  • Assumes the trailing varargs mean the same thing on both overloads
  • Says REST Assured ships its own value-comparison vocabulary rather than using Hamcrest
  • Checks a JSON payload by comparing the whole body to one string literal
  • Believes the path form always hands the matcher a String
open as a page

In REST Assured, what do rootPath, appendRootPath, detachRootPath and noRootPath do to body expectations?

level: juniorimportance: should knowfreq 47%

basics

~20 s

REST Assured's rootPath sets a prefix that every later body(path, matcher) call in the same then() chain is joined onto. appendRootPath extends that prefix, detachRootPath strips a suffix off it, and noRootPath clears it. They affect body expectations only.

open as a page

In REST Assured, where do withArgs(...) and withNoArgs() live, and what do they fill in?

level: middleimportance: should knowfreq 38%

basics

~20 s

withArgs and withNoArgs are static methods on io.restassured.RestAssured that return a List of Argument. You pass them into body, rootPath or appendRootPath, which fills the placeholders in the merged path with String.format. They are not methods on the response.

open as a page

In REST Assured, how do you assert that one field of a response matches another field of the same response?

level: seniorimportance: should knowfreq 36%

basics

~20 s

REST Assured's ResponseAwareMatcher lets a body or header expectation build its matcher from the response itself. Its one method, matcher(response), returns an ordinary Hamcrest matcher. RestAssuredMatchers ships equalToPath, startsWithPath, endsWithPath and containsPath for the common cases.

open as a page

In REST Assured, what does MatcherConfig.errorDescriptionType change in a failed body assertion's message?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

MatcherConfig.errorDescriptionType picks who describes the actual value when a REST Assured body expectation fails. The default, REST_ASSURED, prints the extracted value itself; HAMCREST calls the matcher's own describeMismatch instead. The enum has exactly those two constants.

open as a page