skip to content

In REST Assured's ResponseSpecBuilder, which expectations stack and which replace an earlier call?

level: middleimportance: should knowfreq 36%

answer

  1. same prefix, two different behaviours
  2. lists append, single fields assign
  3. body, header, cookie expectations stack
  4. the second expectStatusCode wins silently

basics

~20 s

A REST Assured ResponseSpecBuilder accumulates body, header and cookie expectations, so repeated calls all run. Status code, status line, content type, response time and log detail are single fields: a second call silently replaces the first. There is no warning.

solid answer

~50 s

Behind `ResponseSpecBuilder` sits one response specification object, and its fields come in two shapes. `expectBody`, `expectHeader`/`expectHeaders`, `expectCookie`/`expectCookies` and `registerParser` write into collections, so every call adds another assertion and all of them run. `expectStatusCode`, `expectStatusLine`, `expectContentType`, `expectResponseTime`, `log(LogDetail)` and `setDefaultParser` write into single fields, so the second call overwrites the first with no warning and no exception. The shared `expect` prefix hides the difference. So `expectStatusCode(200).expectStatusCode(201)` on a fishing-quota catch-logging spec checks **only 201** — a test that should have caught a wrong status quietly stops doing so. If two codes are genuinely acceptable, one call must carry a `Matcher<Integer>` that admits both; two calls will not. The same split is why a shared spec safely gathers body and header checks over time, while one stray `expectContentType` redefines the media type for the whole spec.

code

java · 12 lines
java
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.specification.ResponseSpecification;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;

ResponseSpecification catchLogged = new ResponseSpecBuilder()
        .expectStatusCode(201)
        .expectStatusCode(200)                  // replaces 201: only 200 is checked
        .expectBody("species", equalTo("cod"))
        .expectBody("landedKg", greaterThan(0)) // both body matchers still run
        .build();

go deeper

for a junior

Remember that repeating an expectation is not always additive. If you call expectStatusCode twice on one REST Assured response builder, only the last one survives and the first quietly disappears.

for a middle

Explain the mechanism, not just the rule: expectations backed by a list append, expectations backed by a single field are assigned. Be able to sort the builder's methods into those two groups on the spot.

for a senior

Talk about how this hides in a real suite — a spec edited by three people over a year, carrying two content-type expectations with one silently dead. Say how you would surface it rather than only how to avoid it.

for a principal

Take a position on ownership. Decide who may edit a shared response spec and how such a change is reviewed, because one overwriting line removes a check from every test that uses the spec at once.

## One prefix, two behaviours Every method on `ResponseSpecBuilder` starts with `expect`, which reads as though they all do the same kind of thing. They do not. The builder is a thin façade over a single response-specification object, and inside that object some expectations are held in **collections** and others in **single fields**. A collection-backed call appends, so everything you asked for survives. A field-backed call assigns, so the second call overwrites the first — with no warning, no exception and no log line. The request-side builder at least signals the split in its method names; `ResponseSpecBuilder` gives every method the same prefix, so you have to know it rather than read it. ## Which call is which | `ResponseSpecBuilder` call | Held as | A second call... | |---|---|---| | `expectBody(...)` | a group of body matchers | adds another matcher | | `expectHeader(...)` / `expectHeaders(Map)` | a list of header assertions | adds another assertion | | `expectCookie(...)` / `expectCookies(Map)` | a list of cookie assertions | adds another assertion | | `registerParser(type, Parser)` | a map of content type to parser | registers another type | | `expectStatusCode(...)` | one status-code matcher | replaces the first | | `expectStatusLine(...)` | one status-line matcher | replaces the first | | `expectContentType(...)` | one expected content type | replaces the first | | `expectResponseTime(...)` | one matcher plus a `TimeUnit` | replaces the first | | `log(LogDetail)` | one log detail | replaces the first | | `setDefaultParser(Parser)` | one fallback parser | replaces the first | The split is not arbitrary. A response carries exactly one status code, one status line and one media type, so only one expectation about each can be meaningful. It carries many headers, many cookies and many addressable body paths, so those are lists. ## How it goes wrong on a real suite - **Widening a check.** Someone wants the fishing-quota catch-logging call to accept `200` or `201` and writes both `expectStatusCode` lines. The spec now accepts only the second, and the endpoint returning the other one fails for a reason nobody predicted. - **Merging by copy-paste.** Two shared specs are pasted into one; each declared a content type, and the second wins. The first service's media type is no longer checked anywhere in the suite. - **A temporary override.** A latency ceiling is relaxed with a second `expectResponseTime` during an incident and never removed. The original ceiling is gone, not suspended. - **A stale cookie assertion.** Here the opposite bites: cookie expectations accumulate, so an old `expectCookie("QUOTA_SESSION", "...")` keeps running beside the new one and the spec now demands two different values at once. ## Writing it so it cannot bite 1. Keep exactly one expectation of each scalar kind in any one spec, and put it at the top of the chain where it is visible. 2. When more than one status code is legitimately acceptable, express that in a single `Matcher<Integer>` that admits them all — two calls will not do it. 3. Treat body, header and cookie expectations as a growing list: delete the wrong one rather than adding a corrected one beside it. 4. Prove the spec still checks what you think. There is no querier for a response specification, so run it against a response you know violates the expectation and require the failure. ## Reading a spec someone else wrote When you inherit a shared response spec, read it in two passes: - The **last** `expectStatusCode`, `expectStatusLine`, `expectContentType`, `expectResponseTime`, `log` and `setDefaultParser` in the chain are the ones in force; anything earlier is dead code that still reads as an assertion. - **Every** `expectBody`, `expectHeader`, `expectHeaders`, `expectCookie`, `expectCookies` and `registerParser` is in force, however far apart in the chain they sit. - A duplicate scalar expectation is worth deleting on sight. It is either a mistake or a merge artefact, and leaving it invites the next reader to assume both apply. ## Why there is no warning REST Assured has no notion of a "conflicting" expectation on the builder. The field is simply assigned; nothing compares the new expectation to the old, and nothing logs the replacement. In the intended use — writing one spec top to bottom in one sitting — a second call to the same setter is just the author changing their mind, and warning about it would be noise. The cost lands somewhere else: on a shared spec edited by several people over a year, where a line that looks like an assertion has quietly stopped being one. That is the failure mode the question is really about, and it is why the two shapes are worth memorising rather than looked up.

  • How would you prove a shared response spec still checks the status code you think it does?
    By behaviour, because there is no read-back API: `SpecificationQuerier.query(...)` accepts a `RequestSpecification` only, so a built `ResponseSpecification` cannot be inspected. Attach it to a call that returns a status you know is wrong — a fishing-quota read forced to 500 — and require the failure. If that test passes, the expectation was overwritten.
  • Does calling expectCookie twice with the same cookie name keep both assertions?
    Yes. Cookie expectations go into a list, so both run against the same cookie. `expectCookie("QUOTA_SESSION")` asserts only that it exists, and a later `expectCookie("QUOTA_SESSION", "abc")` adds a value check on top; the response must satisfy both. Nothing deduplicates by name, so a contradictory pair fails rather than the later call winning.

saying these in an interview costs you the question

  • Assumes two expectStatusCode calls mean either code is accepted
  • Thinks every expect method appends because they share a prefix
  • Expects the builder to throw on a conflicting expectation
  • Believes expectBody calls overwrite one another
  • Says calling expectContentType twice checks both media types