In REST Assured, how do you assert that one field of a response matches another field of the same response?
answer
- a matcher built from the response
- one method, matcher(response)
- resolved when the expectation is registered
- equalToPath, startsWithPath, endsWithPath, containsPath
- composer and(...) mixes in plain matchers
basics
~20 sREST 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.
solid answer
~50 sPass an `io.restassured.matcher.ResponseAwareMatcher` where a plain matcher would go. It is a `@FunctionalInterface` with one method — `Matcher<?> matcher(T response)` — so in Java it is written as a lambda taking the response and returning a matcher. REST Assured calls it as the expectation is registered, when the response is already in hand, and then registers whatever ordinary Hamcrest matcher came back. The overloads that accept one are `body(String path, ResponseAwareMatcher)`, `body(String path, List<Argument>, ResponseAwareMatcher)`, `body(List<Argument>, ResponseAwareMatcher)` and `header(String, ResponseAwareMatcher)`. For the common shapes, `io.restassured.matcher.RestAssuredMatchers` ships `equalToPath`, `startsWithPath`, `endsWithPath` and `containsPath`, each of which pulls a second path out of the response and wraps it in the obvious matcher. `ResponseAwareMatcherComposer.and(...)` and `or(...)` combine one with another response-aware matcher or with a plain Hamcrest matcher. Be clear that it proves internal consistency, not correctness — a wrong id echoed into the link still passes.
code
java · 18 linesimport static io.restassured.RestAssured.when;
import static io.restassured.matcher.RestAssuredMatchers.endsWithPath;
import static io.restassured.matcher.ResponseAwareMatcherComposer.and;
import static org.hamcrest.Matchers.*;
// ready-made: the audit link must end with the lot id from the same body
when().get("/lots/LOT-4417")
.then().body("auditHref", endsWithPath("lotId"));
// composed with a plain Hamcrest matcher
when().get("/lots/LOT-4417")
.then().body("auditHref",
and(startsWith("https://saleyard.example/"), endsWithPath("lotId")));
// hand-written, as a lambda over the response
when().get("/lots/LOT-4417")
.then().body("settlementHref", response -> equalTo(
"https://saleyard.example/settlements/" + response.path("currentBid.bidderId")));go deeper
Recognise the shape: a body expectation whose second argument is a lambda taking the response, and know that endsWithPath("lotId") compares against another field of the same payload.
Explain that the interface has a single matcher(response) method, name the four factories on RestAssuredMatchers, and say which overloads accept one.
Be able to say when it runs and why that matters for cost, and to argue the limitation out loud: internal consistency is not correctness, so a self-referential check needs a known-value assertion beside it.
Own the guidance on where self-referential assertions are acceptable in a suite, and how a team avoids a test set that would stay green against a service returning consistently wrong data.
Some invariants are internal to a single payload. A cattle-auction bidding API returning `GET /lots/LOT-4417` might answer: ```json { "lotId": "LOT-4417", "auditHref": "https://saleyard.example/lots/LOT-4417/audit", "currentBid": { "bidderId": "BID-88" }, "settlementHref": "https://saleyard.example/settlements/BID-88" } ``` The links are not arbitrary: each one ends with a value that appears elsewhere in the same document. A plain Hamcrest matcher cannot express that, because you must know the expected string before you write the assertion. `ResponseAwareMatcher` is REST Assured's seam for exactly this case. ## The interface `io.restassured.matcher.ResponseAwareMatcher<T>` is a `@FunctionalInterface` declaring a single method: ```java Matcher<?> matcher(T response) throws Exception; ``` You receive the response and return an ordinary Hamcrest matcher. Because the method is allowed to throw, a lambda body can call something that declares a checked exception; REST Assured rethrows it unchanged rather than wrapping it. ## When it actually runs This is the part worth getting right in an interview. REST Assured does **not** defer the call. When you register the expectation, it invokes `matcher(originalResponse())` immediately, takes the plain matcher that comes back, and registers *that*. There is no second, lazy evaluation later. Two consequences follow: - The response is genuinely available at that moment, because `then()` runs after the request has completed. - Anything expensive inside the lambda — parsing the body again, calling out to another service — happens once, at registration, and happens even if a later expectation is what fails. ## The four ready-made ones `io.restassured.matcher.RestAssuredMatchers` ships four, each a one-line wrapper that extracts a second path and hands it to a standard matcher: | Factory | Equivalent to | |---|---| | `equalToPath(path)` | `equalTo(response.path(path))` | | `startsWithPath(path)` | `startsWith(String.valueOf(response.path(path)))` | | `endsWithPath(path)` | `endsWith(String.valueOf(response.path(path)))` | | `containsPath(path)` | `containsString(response.path(path))` | So `body("auditHref", endsWithPath("lotId"))` says *the audit link ends with the lot id from this same body*, with no literal repeated in the test. The type behaviour differs between them and is worth knowing: - `startsWithPath` and `endsWithPath` stringify whatever the second path returned, so a numeric field is safe. - `containsPath` casts the extracted value to `String`, so pointing it at a number fails at runtime rather than comparing text. - `equalToPath` compares with `equalTo`, so the two values must match in runtime type as well as content — an `Integer` will not satisfy a `Long`. ## Composing them `io.restassured.matcher.ResponseAwareMatcherComposer` provides `and(...)` and `or(...)`. Each accepts response-aware matchers, plain Hamcrest matchers, or a mix, resolves every one against the response, and returns a single response-aware matcher backed by `allOf` or `anyOf`. That is how you write *starts with our public base URL and ends with the lot id* as one expectation. ## Writing your own When the relationship is not a plain prefix or suffix, a lambda covers it: - Read the other field with `response.path("currentBid.bidderId")`. - Build the string or value you expect from it. - Return the matcher — `equalTo`, `greaterThan`, or whatever fits. That keeps the assertion inside the `then()` chain, so a failure is reported with the path name and REST Assured's usual mismatch formatting, rather than as a bare assertion somewhere after the chain has ended. The response-aware form is also accepted on `header(String, ResponseAwareMatcher)`, which is how you assert that a `Location` header on a `201 Created` from `POST /lots/LOT-4417/bids` ends with the bid id the body just reported. The two argument-carrying body overloads take one as well, so a response-aware matcher composes with a root path and `withArgs(...)` exactly like a plain matcher does — there is no separate code path for it once the interface has been resolved. ## When to reach for it, and when not to - **Use it** for self-referential invariants: a link built from an id, a total that echoes a line item, a status that must agree with a nested flag. - **Use it** where writing the expected value as a literal would duplicate what the server just told you and make the test lie when the fixture changes. - **Skip it** when the value is genuinely known in advance; a literal is clearer than indirection. - **Skip it** when the check is really a computation over several fields — extracting the response and asserting in plain code reads better than a lambda doing arithmetic. - **Be careful** that it cannot catch a server that is wrong in both places at once. `endsWithPath("lotId")` still passes if the service returns the wrong lot id and a matching link. That last point is the honest limitation: a response-aware matcher checks internal consistency, not correctness. Pair it with at least one expectation carrying a value the test actually knows.
- Is a ResponseAwareMatcher evaluated lazily, at the moment the assertion is checked?No. REST Assured calls `matcher(response)` as the expectation is registered, with the already-received response, and registers the plain Hamcrest matcher it returns. The work inside your lambda therefore runs once, up front, regardless of whether that expectation or an earlier one ends up failing.
- What is the weakness of asserting one response field against another?It proves internal consistency, not correctness. If the service returns the wrong lot id and builds the link from that same wrong id, `endsWithPath("lotId")` still passes. Always pair it with at least one expectation that carries a value the test itself knows.
saying these in an interview costs you the question
- Thinks the matcher is evaluated lazily during validation
- Assumes ResponseAwareMatcher can only be used on body, not header
- Believes containsPath stringifies a numeric field the way endsWithPath does
- Treats a self-referential check as proof the field is correct
- Reimplements equalToPath by hand instead of using RestAssuredMatchers