A REST Assured suite repeats the same base URI, headers and auth in every test, and when a test fails in CI the output shows only a matcher mismatch. What features of the library would you use to fix both problems?
answer
- RequestSpecBuilder / ResponseSpecBuilder → given().spec / then().spec
- specs compose; later settings override
- log().ifValidationFails() on both sides
- blacklistHeader("Authorization") in LogConfig
- Filter = token refresh, correlation id; statics break parallel runs
basics
~20 sBuild shared RequestSpecification and ResponseSpecification objects with RequestSpecBuilder/ResponseSpecBuilder and apply them with given().spec(...) / then().spec(...). For diagnostics, add log().ifValidationFails() on both request and response, or enableLoggingOfRequestAndResponseIfValidationFails(), and use Filters for cross-cutting concerns like tokens and correlation ids.
solid answer
~40 sTwo separate tools. **Deduplication — specifications.** `new RequestSpecBuilder().setBaseUri(...).setContentType(JSON).setAuth(preemptive().basic(u,p)).addFilter(...).build()` produces a reusable `RequestSpecification` applied as `given().spec(apiSpec)`. `ResponseSpecBuilder` does the same for common expectations (`expectStatusCode(200)`, `expectContentType(JSON)`, `expectResponseTime(lessThan(2000L))`) applied with `then().spec(okJson)`. Specs compose, and per-test calls still override or add. Prefer them to mutating the static `RestAssured.*` globals, which leak across tests and are hostile to parallel runs. **Diagnostics — logging and filters.** `given().log().ifValidationFails()` plus `then().log().ifValidationFails()` prints the full request and response only when an assertion fails — quiet on green, complete on red. `RestAssured.enableLoggingOfRequestAndResponseIfValidationFails()` turns it on suite-wide. Blacklist `Authorization` in `LogConfig` so tokens don't leak. Custom `Filter` implementations cover cross-cutting behaviour: injecting a correlation id, refreshing an expired token, capturing every exchange into the test report.
code
java · 27 linesimport io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.lessThan;
static RequestSpecification apiSpec = new RequestSpecBuilder()
.setBaseUri(System.getProperty("api.baseUri", "http://localhost:8080"))
.setBasePath("/api/v1")
.setContentType(ContentType.JSON)
.setAccept(ContentType.JSON)
.build();
static ResponseSpecification okJson = new ResponseSpecBuilder()
.expectStatusCode(200)
.expectContentType(ContentType.JSON)
.expectResponseTime(lessThan(2000L))
.build();
void listsUsers() {
given().spec(apiSpec).log().ifValidationFails()
.when().get("/users")
.then().spec(okJson).log().ifValidationFails()
.body("size()", org.hamcrest.Matchers.greaterThan(0));
}go deeper
Know that RequestSpecBuilder/ResponseSpecBuilder exist and that given().spec(...) applies shared setup.
Explain spec composition and override rules, and know log().ifValidationFails() as the diagnostic default.
Argue against the static globals on isolation and parallelism grounds, wire failure-only logging with secret blacklisting suite-wide, and use filters for auth refresh and correlation ids.
Treat the spec layer as the suite's configuration boundary — environment injection, auth policy, shared SLAs on response time — and decide what belongs there versus in each test.
## The two problems are independent Repetition is a maintenance problem; opaque failures are an operability problem. REST Assured has a distinct mechanism for each, plus a third — filters — that spans both. ## Specifications kill the repetition A `RequestSpecification` is a reusable bundle of request configuration. Build it once: ```java RequestSpecification apiSpec = new RequestSpecBuilder() .setBaseUri(System.getProperty("api.baseUri", "http://localhost:8080")) .setBasePath("/api/v1") .setContentType(ContentType.JSON) .setAccept(ContentType.JSON) .addHeader("X-Client", "api-tests") .setAuth(RestAssured.preemptive().basic(user, pass)) .addFilter(new RequestLoggingFilter(LogDetail.ALL)) .build(); ``` and apply it per test with `given().spec(apiSpec)`. Anything you add after `spec(...)` is layered on top, and later settings win for single-valued properties, so a test can override the content type or add a header without touching the shared spec. Specs are also composable: `new RequestSpecBuilder().addRequestSpecification(apiSpec).setAuth(adminAuth).build()` produces an admin variant without duplication. `ResponseSpecification` is the mirror image for expectations: ```java ResponseSpecification okJson = new ResponseSpecBuilder() .expectStatusCode(200) .expectContentType(ContentType.JSON) .expectResponseTime(lessThan(2000L)) .build(); given().spec(apiSpec).when().get("/users").then().spec(okJson).body("size()", greaterThan(0)); ``` This is worth more than it first looks: a shared response spec makes suite-wide policy ("every successful read returns JSON in under two seconds") a single edit rather than a hundred. ## Why specs beat the static globals `RestAssured.baseURI`, `RestAssured.port`, `RestAssured.authentication`, `RestAssured.requestSpecification` and friends are **static mutable state on a shared class**. Setting them in per-test setup works until: - one test mutates a global and does not reset it, and an unrelated test fails depending on execution order; - tests run in parallel and stomp on each other's globals — the statics are not per-thread; - someone reads a test in isolation and cannot tell what base URI it hits. If you do use the globals (they are convenient for a single-module suite), set them in one place in suite setup and call `RestAssured.reset()` in teardown. Explicit `given().spec(...)` is safer and self-documenting, and it is the only workable option under parallel execution. ## Logging: quiet on green, complete on red The default failure message names the path, the expected matcher and the actual value — enough when the value is wrong, useless when the request itself was wrong (bad URL, missing header, unexpected content type). The targeted fix: ```java given().spec(apiSpec).log().ifValidationFails() .when().get("/users/{id}", id) .then().log().ifValidationFails() .statusCode(200); ``` The request-side call buffers the outgoing request and prints it only if a `then()` assertion fails; the response-side call does the same for the response. Suite-wide, `RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(LogDetail.ALL)` (or `.addFilter(new ErrorLoggingFilter())`) sets the same behaviour once. `log().all()` prints unconditionally — fine while developing a test, terrible in CI where it buries the real failure in megabytes of output. Before turning logging on, blacklist secrets: ```java RestAssured.config = RestAssured.config() .logConfig(LogConfig.logConfig().blacklistHeader("Authorization")); ``` otherwise every CI run publishes a bearer token to a log aggregator. ## Filters: the extension point A `Filter` sees every request and response and can modify either: ```java public class CorrelationIdFilter implements Filter { @Override public Response filter(FilterableRequestSpecification req, FilterableResponseSpecification res, FilterContext ctx) { req.header("X-Correlation-Id", UUID.randomUUID().toString()); return ctx.next(req, res); } } ``` Typical uses: injecting or refreshing an auth token (retry once on a 401 with a fresh token), stamping a correlation id so a failed test can be traced in server logs, recording every exchange into the test report or an HTML attachment, and timing/metrics. The built-in `RequestLoggingFilter`, `ResponseLoggingFilter` and `ErrorLoggingFilter` are ordinary filters you can add to a spec. Because filters live on the specification, adding one to the shared `apiSpec` applies it to the whole suite from one line. A filter that forgets to call `ctx.next(req, res)` short-circuits the chain and the request is never sent — a confusing failure worth knowing about. ## Putting it together A healthy suite has: one place that builds the request spec from environment configuration, one or two response specs encoding shared expectations, failure-only logging enabled globally with secrets blacklisted, and a small set of filters for auth and traceability. Individual tests then contain only what is specific to the endpoint under test — which is also what makes the failure message meaningful when one goes red at 3am in CI.
- Why prefer given().spec(sharedSpec) over setting RestAssured.baseURI and RestAssured.authentication in setup?Those fields are static mutable state on a shared class, so a test that sets one without resetting affects every later test, producing order-dependent failures. Under parallel execution the problem is worse: the statics are not per-thread, so concurrent tests overwrite each other's base URI or credentials. An explicit specification is passed per request, is visible in the test that uses it, and composes for variants like an admin-authenticated spec.
- What is the difference between log().all() and log().ifValidationFails(), and why does the choice matter in CI?`log().all()` prints the request or response unconditionally, so a green suite dumps every exchange and the eventual real failure is buried in noise. `log().ifValidationFails()` buffers and prints only when a `then()` assertion fails, giving full context exactly when it is needed and silence otherwise. In CI, where you cannot rerun interactively against the same state, failure-only logging is the setting that makes a red build diagnosable without making a green one unreadable.
saying these in an interview costs you the question
- Mutating RestAssured static globals per test and then blaming order-dependent failures on the library.
- Leaving log().all() enabled suite-wide, drowning CI output and leaking Authorization headers.
- Copy-pasting base URI, content type and auth into every test instead of building a specification.
- Thinking a spec is all-or-nothing and cannot be overridden or extended per test.
- Writing a custom filter and forgetting to call ctx.next(...), so the request is never sent.