skip to content

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

level: middleimportance: should knowfreq 34%

answer

  1. one getter per setter, nothing migrates
  2. param is untyped until a verb exists
  3. an empty map usually means wrong map
  4. getURI shows the resolved request line

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.

solid answer

~50 s

The getters mirror the setters exactly. `param(...)` and `RequestSpecBuilder.addParam(...)` land in `getRequestParams()`; `queryParam(...)`/`addQueryParam(...)` in `getQueryParams()`; `formParam(...)`/`addFormParam(...)` in `getFormParams()`. Nothing migrates between them, so a greenhouse readings spec built with `addParam("sensor", "co2")` returns an empty `getQueryParams()` — the commonest surprise on this API. The reason is that `param` is untyped: whether it becomes a query parameter or a form field depends on the HTTP verb, and a specification has none yet, since `getMethod()` reads `null` until a verb is issued. If you want the resolved picture rather than the intent, read `getURI()`, which folds the request parameters into the query string for anything that is not a POST. All three maps come back unmodifiable; `removeParam`, `removeQueryParam` and `removeFormParam` on the filterable specification are how you edit them. The habit worth forming is to check all three, or `getURI()`, before concluding that a parameter went missing.

code

java · 15 lines
java
RequestSpecification readings = new RequestSpecBuilder()
        .setBaseUri("https://climate.greenhouse.test")
        .setBasePath("/api/v2/readings")
        .addParam("sensor", "co2")               // untyped
        .addQueryParam("granularity", "hourly")  // explicitly a query param
        .build();

QueryableRequestSpecification q = SpecificationQuerier.query(readings);

q.getRequestParams().get("sensor");      // "co2"
q.getQueryParams().get("sensor");        // null - a different map
q.getQueryParams().get("granularity");   // "hourly"
q.getFormParams().isEmpty();             // true
q.getMethod();                           // null - no verb yet
q.getURI();                              // .../api/v2/readings?sensor=co2&granularity=hourly

go deeper

for a junior

Remember that REST Assured keeps request, query and form parameters apart, and that you read each one back with the getter matching the setter you used.

for a middle

Explain why an untyped param cannot be resolved on a specification: the destination depends on the HTTP verb, and getMethod is null until one is issued. Name getURI as the resolved view.

for a senior

When a guard test or a merge appears to lose a parameter, check all three maps and getURI before doubting the library. Push the team towards explicit queryParam and formParam so intent survives into the read-back.

for a principal

Set the convention. Deciding that shared specifications only ever use explicit query and form parameters removes a whole class of ambiguity from both the request and the tests that inspect it.

## Three setters, three maps, three getters REST Assured does not keep one parameter bag. A `RequestSpecification` holds separate internal maps, and `QueryableRequestSpecification` exposes each one under the getter that matches the setter which filled it: | You called | The value lands in | You read it back with | |---|---|---| | `param(...)` / `params(...)` / `RequestSpecBuilder.addParam(...)` | the request-parameter map | `getRequestParams()` | | `queryParam(...)` / `addQueryParam(...)` | the query-parameter map | `getQueryParams()` | | `formParam(...)` / `addFormParam(...)` | the form-parameter map | `getFormParams()` | | `pathParam(...)` / `addPathParam(...)` | the named path-parameter map | `getNamedPathParams()` | The mapping is one for one, and it is total: nothing ever migrates between the maps while the specification is alive. Read the wrong one and you get an empty result and a confusing five minutes. ## Why the specification refuses to resolve `param` `param(...)` is deliberately untyped. Where it ends up depends on the HTTP verb — a request parameter becomes part of the query string on most methods and part of the form body on a POST — and a specification does not know the verb. `getMethod()` reads `null` until an HTTP verb has been issued. So the specification stores the value in the request-parameter map and leaves the decision to dispatch time. That is why: - `new RequestSpecBuilder().addParam("sensor", "co2").build()` gives an **empty** `getQueryParams()`. - The same specification gives `"co2"` from `getRequestParams().get("sensor")`. - Nothing you can query before the verb will tell you whether that parameter ends up in the URL or in a form body, because the library has not decided yet either. The practical rule: if you want to know what was *set*, read the map that matches the setter. Use the explicit `queryParam` and `formParam` on the setting side whenever the destination matters, and then the read-back is unambiguous too. ## The getter that does show a resolved request line There is one exception, and it is the useful one. `getURI()` assembles the request line as it stands: it merges base URI, base path, path and the parameters, folding the request parameters into the query string for anything that is not a POST, adding the query parameters, and adding form parameters when the method is GET. So for the greenhouse readings specification: - `getRequestParams()` answers *what did I set untyped?* - `getQueryParams()` answers *what did I set explicitly as a query parameter?* - `getURI()` answers *what does the URL look like right now?* Use the maps for assertions about intent, and `getURI()` for an assertion about the resulting URL. Note that `getURI()` is evaluated against whatever method the specification currently carries, so on a specification that has never been given a verb it behaves as a non-POST. ## Reading the wrong map — the symptoms The failure mode is quiet, which is what makes it worth knowing: - A guard test asserts `getQueryParams()` on a specification built with `addParam(...)` and finds nothing, so the author concludes the querier is broken. - A parameter appears in the request log but not in the map the test reads, so the test is weakened to a log assertion. - A merged specification seems to lose a parameter, when in fact both specifications wrote to different maps and both values are present under different getters. Checking all three maps, or checking `getURI()`, resolves every one of these in seconds. ## Changing what you find All three maps come back through `Collections.unmodifiableMap`, so mutating them throws `UnsupportedOperationException`. Adding goes through the specification's own setters. Removing goes through `FilterableRequestSpecification`, which extends the queryable interface and adds `removeParam`, `removeQueryParam` and `removeFormParam` — that is the pairing to remember: the queryable half reads, the filterable half edits, and a filter receives an object that does both. ## What a good guard test asserts Because all of this is object state, the check is cheap enough to keep permanently. For the greenhouse readings specification, one small test is enough: 1. Assert `getQueryParams().get("granularity")` equals the value the suite depends on, so a change to the factory is caught immediately. 2. Assert `getRequestParams()` is empty if the team's convention is to use only the explicit setters — that turns the convention into something enforced rather than merely agreed. 3. Assert `getURI()` contains the parameters you expect in the URL, which is the closest thing to a wire-level assertion you can make without a server. The last one is worth the extra line. Intent and result are different questions, and a specification that carries the right values in the wrong map still produces a URL you did not intend the moment a verb turns up and resolves them.

  • A parameter shows up in the request log but not in getQueryParams(). What happened?
    It was almost certainly set with `param(...)` or `addParam(...)`, so it lives in the request-parameter map and reads back from `getRequestParams()`. REST Assured only decides that an untyped parameter belongs in the query string when it dispatches the request, which is why the log and the map disagree.
  • How do you remove a parameter you find while inspecting a specification?
    Not through the returned map — it is unmodifiable and throws `UnsupportedOperationException`. Use the specification itself: `removeParam`, `removeQueryParam` and `removeFormParam` are declared on `FilterableRequestSpecification`, which extends the queryable interface, so a filter holding the request can both read and edit.

saying these in an interview costs you the question

  • Expects addParam to show up in getQueryParams
  • Thinks the querier resolves an untyped param by guessing the verb
  • Concludes the querier is broken when a map comes back empty
  • Tries to put a value into the map the querier returned
  • Believes a merge lost a parameter that is simply in another map