In REST Assured, why does then().statusCode(200).onFailMessage("...") never print your message?
answer
- a setter, not an expectation
- then() validates on every call
- put it first after then()
- appended to the message, never a replacement
basics
~20 sEvery expectation on a then() chain validates the moment it is called, so statusCode(200) throws before onFailMessage is reached and the message field is still unset. Put onFailMessage first, immediately after then(), ahead of any expectation it should annotate.
solid answer
~40 s`ValidatableResponse.onFailMessage(String)` is a setter, not an expectation: it stores a string that REST Assured appends to a failure as a final `On fail message: ...` line. On a `then()` chain, though, every real expectation validates the moment you call it, because the response is already in hand and each setter is wrapped in a step that runs the whole assertion closure. So `then().statusCode(200).onFailMessage("publishing call sheet 17")` throws inside `statusCode(200)` and the string is never stored. Written the other way round, the field is set first and the `AssertionError` reads `1 expectation failed.`, then the mismatch, then a blank line, then your message. The library's own integration test and the project wiki both place `onFailMessage` ahead of the expectations for exactly this reason — treat it as the first call after `then()`.
code
java · 17 linesimport static io.restassured.RestAssured.given;
// Wrong: statusCode(200) validates and throws before the label is stored.
given().contentType("application/json").body(publishRequest)
.when()
.put("/call-sheets/17/publish")
.then()
.statusCode(200)
.onFailMessage("publishing call sheet 17");
// Right: the label is set first, so it lands in the AssertionError.
given().contentType("application/json").body(publishRequest)
.when()
.put("/call-sheets/17/publish")
.then()
.onFailMessage("publishing call sheet 17")
.statusCode(200);go deeper
Know the call exists and where it goes: immediately after then(), ahead of the expectations. Remember it adds a line to a failure and never causes one.
Explain eager validation — each expectation on a then() chain runs the whole check as it is called — and derive from that why a trailing onFailMessage is dead code.
Judge when a hand-written per-case label is worth its upkeep against making the assertion itself specific, and how you stop such strings drifting out of date across a large suite.
Own how failing cases identify themselves suite-wide: case naming, the label convention, and whether a hand-written string belongs in the DSL at all rather than in the report.
## What onFailMessage actually does `ValidatableResponseOptions.onFailMessage(String)` records a string on the response specification. When a check later fails, REST Assured appends that string to the `AssertionError` as a final line introduced by `On fail message: `. It is a **label**, nothing more: it does not compare anything, it does not reformat the mismatch text, and it cannot make a passing call fail. The generated message for a failed publish of call sheet 17 looks like this: ``` 1 expectation failed. Expected status code <200> but was <409>. On fail message: publishing call sheet 17 ``` The count line, the mismatch line and the blank separator all come from the assertion machinery; only the last line is yours. ## Why chain order decides whether it appears `then()` is **eagerly validated**. The response is already in hand by the time you start the chain, so `ResponseSpecificationImpl` wraps each expectation setter in a step that records the expectation and then runs the entire assertion closure immediately. The consequence is simple and easy to miss: - `then().statusCode(200)` — the expectation is recorded, validation runs, and if the status is wrong the `AssertionError` is thrown **from inside that call**. - Anything written after it in the chain — including `onFailMessage(...)` — is never evaluated, because the chain never returns. - `onFailMessage(...)` itself is *not* wrapped in that step. It is a plain setter: it stores the string and returns. So `then().statusCode(200).onFailMessage("publishing call sheet 17")` throws while the message field is still unset, and the label silently disappears. Reversed — `then().onFailMessage("publishing call sheet 17").statusCode(200)` — the field is populated first and the label lands in the error. The library's own integration test and the project wiki both write it that way. | Chain | Message appears? | Why | |---|---|---| | `then().onFailMessage(m).statusCode(200)` | yes | the field is set before any expectation validates | | `then().statusCode(200).onFailMessage(m)` | no | `statusCode` throws before the setter is reached | | `then().onFailMessage(m)` alone | never fails | a label is not an expectation, so nothing validates | The `expect()` form behaves differently: there the specification is assembled before the request is sent, so every expectation and the message are recorded first and validated together at the end. Order does not matter there. Only the eager `then()` chain is order-sensitive. ## It is not an assertion The gate that decides whether REST Assured validates anything at all looks for an expected status code, an expected status line, a content type, a response-time matcher, or a header, cookie or body matcher. `onFailMessage` is on none of those lists. Practical consequences: - A `then()` chain carrying only `onFailMessage(...)` validates nothing and passes on any response. - The label never appears on a green run — it exists only in a failure message. - It cannot rescue a case that has no real expectation; it can only annotate one that does. ## Using it well 1. **Put it first.** Make `onFailMessage(...)` the call immediately after `then()`, as a habit, so ordering is never a question in review. 2. **Say what the assertion cannot.** The mismatch already prints the expected and actual code. The label is for the context around it — which fixture, which crew call, which stage of a multi-step flow the case had reached. 3. **Keep it short and stable.** It sits in the failure output, not in a report title, and a long sentence buries the mismatch it is meant to frame. 4. **Prefer a specific assertion where you can.** A message explaining that the case posted a duplicate crew call is worth less than a body expectation proving the service said so. 5. **Watch it drift.** A hand-written label is not checked by anything. When the case is edited to cover a different path, the string usually survives unchanged and then actively misleads. ## The failure this question is really about The whole trap is that both orderings compile, both run, and both fail the case — one of them just quietly loses the annotation you added specifically to make a red CI build readable. Nothing warns you, because from the library's point of view nothing went wrong: an expectation failed and threw, exactly as designed, before a later setter had a chance to run. **In an eagerly validated chain, a setter placed after an expectation is dead code**, and `onFailMessage` is the setter people place there most often.
- Where does onFailMessage sit when you use the expect() form instead of then()?Order stops mattering. `expect()` assembles the whole specification before the request is sent, so every expectation and the message are recorded first and validated together at the end — the library's own test places `onFailMessage` last in that form and it still appears. Only the eagerly validated `then()` chain is order-sensitive.
- What does onFailMessage change about the mismatch text itself?Nothing. REST Assured still builds the count line and each matcher's own expected-versus-actual text; your string is appended after a blank line as `On fail message: <text>`. It labels the failure, it does not replace, reformat or suppress it, and it never appears on a passing call.
saying these in an interview costs you the question
- Thinks onFailMessage replaces the mismatch text
- Places it after the expectations in a then() chain
- Counts it as an assertion that can fail a case
- Expects the label to appear on a passing call too
- Believes chain order never matters in the then() DSL