skip to content

In REST Assured, how do you prove which base URI survived given().baseUri(...).spec(sharedSpec)?

level: seniorimportance: must knowfreq 36%

answer

  1. do not chain straight into the verb
  2. hold the assembled chain in a local
  3. query it, then assert getBaseUri
  4. call order decides, so read it back

basics

~20 s

REST Assured can answer that without a server: keep the assembled chain in a variable, pass it to SpecificationQuerier.query, and assert getBaseUri on the QueryableRequestSpecification it returns. The read-back shows exactly which value the merge left behind.

solid answer

~50 s

`given()` returns a `RequestSpecificationImpl`, so you can hold the half-built chain in a local, hand it to `SpecificationQuerier.query(...)` and read it before any verb fires. Assert `getBaseUri()`, `getBasePath()`, `getPort()` or `getHeaders().getValue("X-Greenhouse-Site")` — whichever the argument is about — and you have a deterministic answer in a test with no server, no port and no flake. What you see is that `spec(...)` merges at the point of the call and the incoming specification wins on scalars: `given().baseUri("https://staging.greenhouse.test").spec(climateSpec)` reports the shared base URI, while `given().spec(climateSpec).baseUri("https://staging.greenhouse.test")` reports the override. Headers, cookies, parameters, multipart entries and filters accumulate instead, so both specifications' headers survive either ordering. Three practical points: query after the last mutation you care about, because the view is live; remember `getMethod()` is `null` until a verb is issued; and keep the assertion as a permanent regression test on the specification factory rather than a throwaway debug print.

code

java · 21 lines
java
RequestSpecification climateSpec = new RequestSpecBuilder()
        .setBaseUri("https://climate.greenhouse.test")
        .addHeader("X-Greenhouse-Site", "delta-4")
        .build();

// spec(...) applied last
RequestSpecification specLast = given()
        .baseUri("https://staging.greenhouse.test")
        .spec(climateSpec);

assertThat(SpecificationQuerier.query(specLast).getBaseUri())
        .isEqualTo("https://climate.greenhouse.test");

// override applied last
RequestSpecification overrideLast = given()
        .spec(climateSpec)
        .baseUri("https://staging.greenhouse.test");

QueryableRequestSpecification q = SpecificationQuerier.query(overrideLast);
assertThat(q.getBaseUri()).isEqualTo("https://staging.greenhouse.test");
assertThat(q.getHeaders().getValue("X-Greenhouse-Site")).isEqualTo("delta-4");

go deeper

for a junior

Know that a REST Assured chain can be held in a variable and inspected before the verb runs. Be able to name SpecificationQuerier.query as the way to look inside it.

for a middle

Explain that spec(...) merges where it is called, so call order decides scalars such as base URI while headers and parameters accumulate. Show how a getBaseUri assertion demonstrates it.

for a senior

Demonstrate the production habit: settle the disagreement with a read-back assertion, then leave that assertion in the suite as a guard on the shared specification factory so the answer cannot rot.

for a principal

Own the trade-off between one shared specification with per-test overrides and several purpose-built specifications. Decide what the suite guarantees about assembly order and how that guarantee is enforced.

## The question a request log answers too late Two engineers disagree about a greenhouse climate suite: one says the shared `climateSpec` points at `https://climate.greenhouse.test`, the other says the per-test `given().baseUri(...)` override wins. The instinctive move is to run the test and read the request log. That is a bad instrument for this question. It needs a reachable service, it costs a round trip per experiment, it prints only the request line rather than the assembled specification, and the answer evaporates the moment the branch is merged — nothing stops the same argument recurring next quarter. REST Assured has a direct instrument instead. `RestAssured.given()` returns a `RequestSpecificationImpl`, which implements `QueryableRequestSpecification`, so the chain you are building is readable at any point before a verb is issued. ## The technique Three steps, and none of them touch the network: 1. Stop chaining into the verb. Assign the half-built chain to a local `RequestSpecification`. 2. Pass that local to `SpecificationQuerier.query(...)` and keep the returned `QueryableRequestSpecification`. 3. Assert the getter that matches the argument — `getBaseUri()`, `getBasePath()`, `getPort()`, `getHeaders().getValue("X-Greenhouse-Site")`, `getQueryParams().get("granularity")`, `getAuthenticationScheme()`, `getDefinedFilters()`. Because the whole thing is object reads, the result is deterministic: no port, no fixture service, no flake, and it runs in milliseconds inside the same test class as everything else. ## What the read-back reveals Run it against both orderings and the mechanism becomes visible immediately. `spec(...)` merges the incoming specification **at the point of the call**, and the incoming values overwrite the scalar settings while the collection-shaped ones accumulate: | Chain | `getBaseUri()` reports | Why | |---|---|---| | `given().baseUri("https://staging.greenhouse.test").spec(climateSpec)` | the shared spec's URI | `spec(...)` ran last and overwrote the scalar | | `given().spec(climateSpec).baseUri("https://staging.greenhouse.test")` | the override | the setter ran after the merge | The same read-back shows the other half of the story. Query `getHeaders()` after either ordering and both the shared `X-Greenhouse-Site` header and any per-test header are present, because headers, cookies, parameters, multipart entries and filters accumulate rather than replace. Scalars — base URI, base path, port, request body, authentication scheme, proxy — carry the last value written. You do not have to trust that summary. That is the point of the technique: the assertion tells you what this version of the library does with these two specifications, and it keeps telling you. ## Turning a diagnosis into a permanent guard Once the read-back has settled the argument, do not delete it. Promote it: - Keep one test per specification factory that asserts base URI, base path, content type and the headers the suite depends on. - Add a second test for the ordering the team actually uses, so a refactor that moves `spec(...)` to the end of a chain fails one small test rather than redirecting the entire suite at the wrong host. - Assert `getDefinedFilters()` alongside, so a filter silently dropped by a merge is caught in the same place. - Prefer explicit assertions over printing. A print in a debugging session answers today's question; an assertion answers it every time someone edits the factory. ## Three things that catch people out - **The view is live.** `query(...)` does not snapshot. If you query a chain and then keep calling setters on it, the values you read afterwards have moved. Query last, or copy the values into locals immediately. - **`getMethod()` is `null` before a verb.** Anything derived from the verb — the method itself, and the precise output of `getURI()` — is only meaningful once a verb has supplied one. Base URI, base path, headers, parameters and filters are all readable long before that. - **`getPort()` is derived, not stored.** It reports the port of the resolved target URI, so a base URI with no explicit port does not report a friendly default just because you expected one. When the argument is about ports, assert `getURI()` too. ## Why this beats the alternatives The alternatives are guessing from documentation, reading a request log after a real call, or stepping through the merge in a debugger. The first is not evidence; the second needs an environment and only shows the wire; the third does not survive the session. Reading the assembled specification back is the only one of the four that is cheap, exact and repeatable, which is why it belongs in the suite rather than in a chat thread.

  • Why assert this in a test rather than read it from the request log during a run?
    The log needs a reachable service and a round trip, prints only the request line, and disappears with the run. A querier assertion is an object read: it needs no environment, covers base path, headers, parameters and filters as well, and keeps failing every time someone reorders the chain.
  • You query the chain and then add another header. Does the value you already read change?
    The `QueryableRequestSpecification` is a live view of the same object, not a snapshot, so a later `getHeaders()` call through it shows the new header. A `String` you already pulled out is of course unaffected. The safe habit is to query after the last mutation, or copy values out immediately.
  • Which settings does the read-back show accumulating instead of being overwritten?
    Headers, cookies, request parameters, query parameters, form parameters, named path parameters, multipart entries and filters all accumulate across a merge. Scalars — base URI, base path, port, request body, authentication scheme, proxy specification, URL-encoding flag — carry whichever value was written last.

saying these in an interview costs you the question

  • Insists only a request log can show which value won
  • Says the per-request setter always beats a merged specification
  • Chains straight into the verb, leaving nothing to query
  • Treats the queried view as a snapshot taken at query time
  • Reads the answer once in a debugger and never encodes it as a test