skip to content

Inspecting a Built Spec

Reading a built request specification back to see which header, parameter or base URI survived a merge. Almost nobody knows the query helper exists, and nothing else answers that question.

part ofREST Assuredoverview, primer and where to startread it →
on this pageshow

questions

5

In REST Assured, how do you read a header or base URI back off a built RequestSpecification?

level: middleimportance: must knowfreq 42%

answer

  1. a spec can be read, not only sent
  2. one static helper, no HTTP involved
  3. query(spec) returns a read-only view
  4. getDefinedFilters, never getFilters

basics

~20 s

REST Assured reads a built request specification back through SpecificationQuerier.query(spec), which returns a QueryableRequestSpecification. That read-only view exposes getters such as getBaseUri, getBasePath, getHeaders, getCookies and getDefinedFilters. No request is sent, so it runs in a plain unit test.

solid answer

~50 s

`io.restassured.specification.SpecificationQuerier.query(spec)` hands you a `QueryableRequestSpecification` — a read-only window onto what a `RequestSpecification` holds. It works because everything REST Assured builds (`new RequestSpecBuilder().build()`, `given()`, `RestAssured.requestSpecification`) is a `RequestSpecificationImpl`, which already implements that interface; `query` is a guarded cast that throws `IllegalArgumentException` for anything else. From there you read `getBaseUri()`, `getBasePath()`, `getPort()`, `getContentType()`, `getHeaders()`, `getCookies()`, `getBody()`, `getRequestParams()`, `getQueryParams()`, `getFormParams()`, `getNamedPathParams()`, `getConfig()` and `getDefinedFilters()`. `getHeaders()` returns REST Assured's `Headers` type, so you finish with `getValue("X-Greenhouse-Site")`. Because nothing is sent, a shared greenhouse-climate specification can be asserted in an ordinary test with no server and no port, which makes a broken specification factory fail one fast test instead of the whole suite. Two caveats: the view is live rather than a snapshot; and it reports what you *defined* — the default `Accept: */*` header and REST Assured's own internal filters appear only when the call goes out.

code

java · 20 lines
java
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.*;

RequestSpecification climateSpec = new RequestSpecBuilder()
        .setBaseUri("https://climate.greenhouse.test")
        .setBasePath("/api/v2")
        .addHeader("X-Greenhouse-Site", "delta-4")
        .addQueryParam("granularity", "hourly")
        .setContentType("application/json")
        .build();

QueryableRequestSpecification q = SpecificationQuerier.query(climateSpec);

q.getBaseUri();                               // "https://climate.greenhouse.test"
q.getBasePath();                              // "/api/v2"
q.getHeaders().getValue("X-Greenhouse-Site"); // "delta-4"
q.getQueryParams().get("granularity");        // "hourly"
q.getContentType();                           // "application/json"
q.getHeaders().hasHeaderWithName("Accept");   // false - added at send time
q.getDefinedFilters();                        // unmodifiable, no SendRequestFilter yet

go deeper

for a junior

Know that REST Assured can read a RequestSpecification back with SpecificationQuerier.query(spec), and that nothing is sent when you do. Be able to name two getters, such as getBaseUri and getHeaders.

for a middle

Explain why the call is a guarded cast: everything RequestSpecBuilder and given() produce already implements QueryableRequestSpecification. Be ready to say which values are missing because REST Assured only fills them at send time.

for a senior

Show the production use: a fast test per shared specification factory that asserts base URI, headers and filters with no server running, so a broken spec fails one cheap test instead of reddening the whole suite.

for a principal

Own the convention. Decide whether shared specifications are built by factories with querier-backed guard tests, and weigh that cost against discovering the same defects only through long end-to-end runs.

## Why a specification needs a read side A REST Assured `RequestSpecification` looks write-only. You call `setBaseUri`, `addHeader` and `addQueryParam` on a `RequestSpecBuilder`, call `build()`, and from then on the only evidence of what is inside it is a request that actually went out over the network. For the specification every test in a greenhouse climate suite starts from, that is a slow and indirect feedback loop: `climateSpec` can carry the wrong site header for a hundred tests, and the cheapest way to find out is a round trip against a running service. `io.restassured.specification.SpecificationQuerier` closes it. It is one static method: ```java QueryableRequestSpecification queryable = SpecificationQuerier.query(spec); ``` Nothing is sent, no port is bound, nothing is stubbed. It is an ordinary object read. ## How query(...) works It is neither reflection nor a copy. Everything REST Assured itself produces — `new RequestSpecBuilder().build()`, `RestAssured.given()`, the static `RestAssured.requestSpecification` — is a `RequestSpecificationImpl`, and that class implements `FilterableRequestSpecification`, which extends `QueryableRequestSpecification`. `query(...)` performs an `instanceof` check and a cast; hand it a specification you implemented yourself and it throws `IllegalArgumentException`. Two consequences follow: - The returned view is **live**, not a snapshot — it is the same object seen through a narrower interface, so values change under you if you keep calling setters. Query after the last mutation you care about. - Inside a filter you never need `SpecificationQuerier` at all: the `FilterableRequestSpecification` handed to you already *is* a `QueryableRequestSpecification`, and it adds the mutators (`removeHeader`, `replaceHeader`, `removeQueryParam`, `path`) that the read-only interface lacks. ## What you can read | What you want to know | Getters | |---|---| | Where the call lands | `getBaseUri()`, `getBasePath()`, `getPort()`, `getURI()`, `getMethod()`, `getDerivedPath()`, `getUserDefinedPath()` | | What travels with it | `getHeaders()`, `getCookies()`, `getContentType()`, `getBody()`, `getMultiPartParams()` | | Parameters | `getRequestParams()`, `getQueryParams()`, `getFormParams()` | | Path placeholders | `getPathParams()`, `getNamedPathParams()`, `getUnnamedPathParams()`, `getUnnamedPathParamValues()`, `getPathParamPlaceholders()`, `getUndefinedPathParamPlaceholders()` | | Machinery | `getDefinedFilters()`, `getAuthenticationScheme()`, `getProxySpecification()`, `getConfig()`, `getHttpClient()` | Two details worth memorising: - The filter getter is **`getDefinedFilters()`**. There is no `getFilters()` on this interface, and guessing that name is the single commonest way to fail to compile against it. - `getHeaders()` returns REST Assured's own `Headers` type, so you finish the call with `getValue("X-Greenhouse-Site")`, `getValues(...)` or `hasHeaderWithName(...)`; `getCookies()` returns `Cookies` with the same shape. The parameter maps and the filter list come back through `Collections.unmodifiableMap` and `Collections.unmodifiableList`. Calling `put` or `add` on one throws `UnsupportedOperationException`; changing a specification goes through the specification's own API instead. ## Defined, not sent The most useful thing to internalise is that the querier reports the **specification**, not the wire. Several things REST Assured puts on a request are decided when the call goes out, so they are absent from a spec you query beforehand: - The default `Accept: */*` header, which is only added when you never set one yourself. - The `Content-Type` derived from form parameters or a multipart body — `getContentType()` simply reads back the `Content-Type` header that `contentType(...)` wrote. - `getMethod()`, which is `null` until an HTTP verb has been issued, and with it the exact shape of `getURI()`. - REST Assured's internal filters: `SendRequestFilter` (appended last, and the thing that actually sends), `TimingFilter`, the form-auth and CSRF filters, and the logging filters that `enableLoggingOfRequestAndResponseIfValidationFails()` installs. One thing resolves earlier than people expect: `body(pojo)` serialises immediately, so `getBody()` hands back the serialised `String`, not your object. ## Where it pays off 1. **Guard a shared specification.** One test per factory method that queries the built spec and asserts base URI, base path, content type and the site header. It runs in milliseconds against no server and catches the copy-paste that would otherwise redden every test in the suite. 2. **Explain a precedence surprise.** When a per-test override and a shared specification disagree, querying at the exact point of the chain shows which value survived, with no log reading and no round trip. 3. **Document the harness.** A test that prints or asserts `getDefinedFilters()` and `getConfig()` is a living description of what the suite actually attaches to every call. ## The edge of the mechanism `SpecificationQuerier.query(...)` accepts a `RequestSpecification` and nothing else. There is no response-side querier: a `ResponseSpecification` is readable only through the `FilterableResponseSpecification` a filter receives, and values from a real response come off the response object, never off a specification.

  • What does SpecificationQuerier.query() do when the specification is not one REST Assured built?
    It throws `IllegalArgumentException`. The method is a guarded cast: it checks `instanceof QueryableRequestSpecification`, casts when the check passes, and rejects everything else with a message naming the interface. Every specification REST Assured itself creates passes, so in practice you only see this if you wrote your own `RequestSpecification` wrapper.
  • Is the object returned by query() a snapshot of the specification at that moment?
    No. It is the same underlying object exposed through a read-only interface, so setters called afterwards are visible through it. If you need a stable record, copy the values you care about into locals at the moment you query, rather than holding the `QueryableRequestSpecification` and reading it later.
  • Can you change a specification through the querier?
    No. The maps and the filter list come back unmodifiable, and the interface declares only getters. To change a specification you use its own API — `noFilters()`, `noFiltersOfType(...)`, or the `remove*` and `replace*` methods that `FilterableRequestSpecification` adds when a filter is holding the request.

The specification is not opened and copied; it is put behind a window. You are looking at the same object through an interface that only lets you read it.

saying these in an interview costs you the question

  • Says you must send a request to see what a spec contains
  • Calls the filter getter getFilters instead of getDefinedFilters
  • Thinks SpecificationQuerier also reads a ResponseSpecification
  • Expects the queried headers to include the default Accept header
  • Tries to mutate the specification through the maps the querier returns
  • Assumes the returned view is an immutable snapshot
open as a page

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

level: seniorimportance: must knowfreq 36%

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.

open as a page

In REST Assured, which querier getters show a spec's path placeholders and their values?

level: juniorimportance: should knowfreq 30%

basics

~20 s

REST Assured's QueryableRequestSpecification names every brace in a path with getPathParamPlaceholders and only the unfilled ones with getUndefinedPathParamPlaceholders. The values come back from getPathParams, getNamedPathParams, getUnnamedPathParams and getUnnamedPathParamValues, and getDerivedPath shows the path with known values applied.

open as a page

In REST Assured, why do getRequestParams, getQueryParams and getFormParams return different maps?

level: middleimportance: should knowfreq 34%

basics

~20 s

REST Assured stores parameters in three separate maps, and the querier mirrors them one for one: param feeds getRequestParams, queryParam feeds getQueryParams, formParam feeds getFormParams. A specification never resolves an untyped param, so reading the wrong map returns nothing.

open as a page

In REST Assured, what does a queried spec's getDefinedFilters() show and what is missing?

level: seniorimportance: should knowfreq 26%

basics

~20 s

getDefinedFilters on REST Assured's QueryableRequestSpecification returns an unmodifiable list of the filters you registered, plus any the specification snapshotted or merged in. The internal filters REST Assured appends at send time, including SendRequestFilter, are not there yet.

open as a page