In REST Assured, how does then().spec(respSpec) differ from expect().spec(respSpec)?
answer
- same name, different machinery
- one validates, one merges
- the response already exists in then()
- expect() can still be overwritten
basics
~20 sthen().spec(respSpec) does not merge: ValidatableResponseOptionsImpl runs that specification's expectations directly against the response already received, so earlier assertions in the chain still apply. expect().spec(respSpec) merges through SpecificationMerger, where the incoming spec overwrites the expected status code.
solid answer
~50 sThe two calls share a name and nothing else. `then().spec(bookingCreated)` reaches `ValidatableResponseOptionsImpl.spec(...)`, which forces the body to a string, wires the current config and log repository onto the incoming specification, and then calls `validate(response)`. It is a second round of validation against a response you already hold — nothing is merged and nothing you asserted earlier in the chain is superseded, because `then()` validates eagerly call by call. `expect().spec(bookingCreated)` reaches `ResponseSpecificationImpl.spec(...)`, which calls `SpecificationMerger.merge`. There the incoming specification overwrites `contentType`, `bodyRootPath`, `expectedStatusCode`, `expectedStatusLine`, `expectedResponseTime` and the response log detail, while `bodyMatchers`, `cookieAssertions` and `headerAssertions` accumulate. So `expect().statusCode(202).spec(spec)` loses the `202`; `then().statusCode(202).spec(spec)` keeps it and enforces both. Practically, `then().spec(...)` composes — a shared block of expectations can never be quietly weakened — while `expect().spec(...)` is the side on which a single case can genuinely override one.
code
java · 16 linesimport static io.restassured.RestAssured.given;
// then().spec(...) validates - both expectations must hold
given().spec(trekSpec)
.when()
.post("/bookings")
.then()
.statusCode(201) // runs eagerly, right here
.spec(bookingCreated); // its expectations run in addition
// expect().spec(...) merges - bookingCreated's expected status
// code overwrites the 202 written before it
given().spec(trekSpec)
.expect().statusCode(202).spec(bookingCreated)
.when()
.post("/bookings");go deeper
Know that then().spec(respSpec) simply runs a reusable block of expectations against the response, and that anything you asserted before it in the chain still has to pass.
Explain the mechanism: one call routes to validate(response) on an existing response, the other to SpecificationMerger while the expectation set is still being assembled.
Use the distinction deliberately — then().spec() for composable expectation blocks that nothing can quietly weaken, expect().spec() when a case genuinely needs to override a shared expectation.
Decide whether shared response specifications in the suite are contracts that may never be relaxed or defaults that a case may override, and pick the attachment side that enforces that decision.
## Two calls with the same name and different machinery REST Assured spells response-specification attachment `spec(...)` in two places, and only one of them goes through `SpecificationMerger`. Because the method name is identical, people carry the merge rules across from one to the other and get the wrong answer about which expectation survives. - `then().spec(bookingCreated)` is `ValidatableResponseOptionsImpl.spec(...)`. It **validates**. - `expect().spec(bookingCreated)` is `ResponseSpecificationImpl.spec(...)`. It **merges**. ## What then().spec(...) actually does By the time you are inside a `then()` chain the request has been sent and the response is in hand. `ValidatableResponseOptionsImpl.spec(...)` therefore has nothing to build up. It: 1. Forces the body to a string, so the response can be read more than once. 2. Copies the current configuration onto the incoming specification, and wires the log repository through when logging-if-validation-fails is enabled. 3. Calls `responseSpecification.validate(response)`. That is the whole method. There is no assignment of `expectedStatusCode`, no replacement of `bodyRootPath`, no discarding of anything. The specification's expectations are simply run, right there, against the response you already have. The consequence people miss: **assertions you wrote earlier in the same `then()` chain are not superseded.** `then()` validates eagerly, one call at a time, so `statusCode(201)` has already run and either passed or thrown before `spec(...)` is reached. Both sets of expectations apply, and the first one to fail is the one you see in the report. ## What expect().spec(...) does instead `expect()` returns a `ResponseSpecification` that is still being assembled — the request has not been sent. Attaching there calls `SpecificationMerger.merge(this, incoming)`, which splits fields the familiar way: - **Overwritten:** `contentType`, `bodyRootPath`, `expectedStatusCode`, `expectedStatusLine`, `expectedResponseTime`, `responseLogDetail` and the default parser. - **Accumulated:** `bodyMatchers`, `cookieAssertions`, `headerAssertions` and any registered custom parsers. So `expect().statusCode(202).spec(bookingCreated)` ends up expecting whatever status code `bookingCreated` holds — the `202` is gone. Written the other way round, `expect().spec(bookingCreated).statusCode(202)`, the `202` wins, because it is applied after the merge. Call order matters here exactly as it does on the request side. ## Side by side | | `then().spec(respSpec)` | `expect().spec(respSpec)` | |---|---|---| | When it runs | after the response arrives | while the expectation set is built | | Mechanism | `validate(response)` | `SpecificationMerger.merge` | | Earlier `statusCode(...)` | still applies | overwritten | | Body matchers | run in addition | accumulated, then all run | | Returns | `ValidatableResponse` | `ResponseSpecification` | ## Why the distinction is practical, not academic - A reusable "created" block attached with `then().spec(...)` composes cleanly: a test may add its own assertions before or after it and nothing is quietly dropped. - A test that wants to *relax* a shared expectation cannot do it with `then().spec(...)`. There is no overwrite, so if the shared spec insists on `201` you cannot allow `200` as well — you need a different specification. - On the `expect()` side you can relax an expectation, but only by ordering: attach the shared spec first and restate the status code afterwards. - `ResponseSpecBuilder.addResponseSpecification(...)` uses the merging overload too, so the same overwrite rules apply when you compose one response specification out of another. ## A worked pair on the booking API ```java // both expectations run - 201 and whatever bookingCreated asserts given().spec(trekSpec) .when().post("/bookings") .then() .statusCode(201) .spec(bookingCreated); // bookingCreated's status code overwrites the 202 written before it given().spec(trekSpec) .expect().statusCode(202).spec(bookingCreated) .when().post("/bookings"); ``` ## Where each one is the right tool - Use `then().spec(bookingCreated)` for a reusable expectation block you want enforced no matter what else a test asserts. Nothing in the chain can weaken it, and a case is free to add its own checks before or after. - Use `expect().spec(bookingCreated)` when the shared specification is a *default* and some case has to depart from it. Attach it first and restate the differing expectation afterwards. - Use `ResponseSpecBuilder.addResponseSpecification(...)` when you are composing one reusable specification out of another; it runs the merging overload, so the same overwrite rules apply. - Reach for a second, looser specification rather than trying to relax an expectation that `then().spec(...)` has already committed you to — there is no mechanism for that. ## The one-line summary `then().spec(...)` adds a second round of validation to a response you already hold; `expect().spec(...)` edits the expectation set before the request goes out, and the incoming specification wins on the single-valued expectations. Same word, different verb.
- Can a single test relax a status code that a shared response specification insists on, using then().spec(...)?No. `then().spec(...)` only adds validation, so the shared specification's expectation always runs and there is nothing to overwrite. Either attach the spec on the `expect()` side and restate the status code after it, or build a second, looser response specification for that case.
- Why does then().spec(...) force the response body to a string before validating?A response body is read from a stream, and a stream can be consumed once. `ValidatableResponseOptionsImpl.spec(...)` calls `response.asString()` first so the incoming specification's matchers, and any assertion that runs after it, all read the same buffered body instead of an exhausted stream.
saying these in an interview costs you the question
- Assuming then().spec() replaces assertions made earlier in the chain
- Expecting the same merge rules on both response-side spec() calls
- Believing then().spec() defers validation until the chain ends
- Thinking a response spec can loosen an expectation it merges into