skip to content

Request DSL & Assertions

Writing an API case end to end: building the request, asserting on the JSON body, pulling a value out of the response, and reusing specifications across a suite. Interviewers start here.

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

questions

6

What is the REST Assured library used for on the JVM, and how does its given/when/then request DSL work?

level: juniorimportance: must knowfreq 65%

answer

  1. given = request, when = verb, then = assert
  2. body(GPath, Hamcrest matcher)
  3. static import RestAssured.* + Matchers.*
  4. needs a running server — it only speaks HTTP
  5. io.restassured (5.x) vs old com.jayway

basics

~20 s

REST Assured is a Java library for testing HTTP/REST APIs. You write one fluent chain: given() sets up the request (headers, body, params, auth), when() fires the HTTP call (get/post/put/delete), then() asserts on the response (status, headers, body values).

solid answer

~40 s

REST Assured is a JVM library that makes calling and asserting on a real HTTP API read almost like a sentence. The chain has three stages: - **given()** — request specification: base URI/path, headers, query and path params, cookies, auth, content type, request body (a String, a Map, or a POJO that gets serialized). - **when()** — the actual HTTP call: `get("/users/{id}", 42)`, `post("/users")`, etc. This performs a real network request against a running server. - **then()** — response validation: `statusCode(200)`, `contentType(JSON)`, `header(...)`, `body("name", equalTo("Ann"))` where the matcher is a Hamcrest matcher and the path is a GPath expression. You normally `import static io.restassured.RestAssured.*` and `org.hamcrest.Matchers.*`. `RestAssured.baseURI`/`port`/`basePath` set global defaults so tests don't repeat the host. Failures throw an `AssertionError` naming the expected vs actual value, so it plugs into any test runner.

code

java · 16 lines
java
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;

given()
    .baseUri("http://localhost:8080")
    .contentType(io.restassured.http.ContentType.JSON)
    .header("X-Trace-Id", "abc-123")
    .body("{\"name\":\"ann\",\"email\":\"[email protected]\"}")
.when()
    .post("/users")
.then()
    .statusCode(201)
    .contentType(io.restassured.http.ContentType.JSON)
    .header("Location", notNullValue())
    .body("name", equalTo("ann"));

go deeper

for a junior

Be able to write the three-stage chain from memory and explain what goes in each stage, plus the fact that a server must already be running.

for a middle

Add the mechanics: verbs send the request, GPath + Hamcrest drive body assertions, POJOs are serialized by Jackson/Gson based on content type.

for a senior

Talk about the static global config on RestAssured, when the chain belongs behind a reusable specification, and how failures are surfaced (log().ifValidationFails()).

for a principal

Frame where an HTTP-level suite belongs in the overall testing strategy for the service and what the cost of owning one against a deployed environment really is.

## What REST Assured is REST Assured is a Java (JVM) library for **black-box testing HTTP APIs**. It is not a test runner and not a mocking framework — it is an HTTP client with a validation DSL bolted on. You point it at a running service (a locally started app, a container, a deployed environment), send a real request, and assert on the real response. Everything it does could be done with a raw HTTP client plus manual JSON parsing; the value is that it collapses "build request → send → parse body → assert" into one readable chain. ## The three stages The DSL is deliberately shaped like the Given/When/Then phrasing of behaviour specifications: **given()** returns a *request specification*. Everything you attach here describes the request before it is sent: - `baseUri(...)`, `port(...)`, `basePath(...)` - `header("X-Trace-Id", id)`, `headers(map)`, `cookie(...)` - `queryParam("page", 2)`, `pathParam("id", 42)`, `formParam(...)` - `contentType(ContentType.JSON)`, `accept(ContentType.JSON)` - `body(objectOrString)` — a String is sent as-is; a Map or POJO is serialized to JSON (Jackson/Gson on the classpath) or XML (JAXB) based on the content type - `auth().basic(...)`, `auth().oauth2(token)` **when()** is a readability no-op that returns the same specification; the *next* call is the verb, and that is what actually executes the request: `get`, `post`, `put`, `patch`, `delete`, `head`, `options`. Path templates are supported inline: `get("/users/{id}", 42)`. **then()** returns a *response specification* used for validation: - `statusCode(200)` or `statusCode(is(both(greaterThanOrEqualTo(200)).and(lessThan(300))))` - `contentType(ContentType.JSON)` - `header("Location", containsString("/users/"))` - `time(lessThan(2000L))` - `body("<gpath>", <hamcrest matcher>)` — the workhorse Each `body(...)` call takes a **GPath** expression (Groovy's path language, which for JSON looks like `user.address.city` and for arrays supports `items[0].id`, `items.size()`, `items.name`) and a **Hamcrest matcher** (`equalTo`, `hasItems`, `notNullValue`, `containsInAnyOrder`, …). This is why REST Assured pulls in Hamcrest and Groovy transitively. Optionally the chain continues with `.extract()` to pull values back into Java, or `.and()` purely for readability. ## A minimal example ```java given() .baseUri("http://localhost:8080") .contentType(ContentType.JSON) .body(new CreateUser("ann")) .when() .post("/users") .then() .statusCode(201) .body("name", equalTo("ann")); ``` ## Static imports and global config The DSL is designed around `import static io.restassured.RestAssured.*` (giving you `given()`, `when()`, `get()`) and `import static org.hamcrest.Matchers.*`. Static fields on `RestAssured` — `baseURI`, `port`, `basePath`, `defaultParser`, `filters(...)`, `requestSpecification`, `responseSpecification` — set suite-wide defaults. Because these are **static mutable globals**, they should be set once in suite setup and reset (`RestAssured.reset()`) rather than tweaked per test; per-test tweaking is a classic source of cross-test interference and problems under parallel execution. ## What it does not do - It does **not** start your application. Something else must have the server listening — the test framework's server-start support, a container, or a deployed environment. REST Assured only speaks HTTP to it. - It does **not** replace unit-level testing of internal classes; it exercises the API surface end to end, including serialization, routing, filters, and security. - Assertions produce `AssertionError`, so any JVM test runner reports them normally; REST Assured has no lifecycle of its own. ## Failure output When a `body(...)` assertion fails, the error message shows the GPath, the expected matcher description and the actual value. It does *not* dump the whole response by default — you add `log().ifValidationFails()` on the request and/or response side for that, which is essential for diagnosing failures in CI where you cannot rerun interactively. ## Packaging and versions The modern coordinates are `io.rest-assured:rest-assured` with the package `io.restassured` (5.x, Java 8+). Very old code uses `com.jayway.restassured` (2.x) — the package rename is a common upgrade stumbling block. Companion modules exist for extras: `json-schema-validator` (JSON Schema assertions), `xml-path`/`json-path` (standalone path parsing), and Kotlin/Scala extension modules.

  • Does when() actually send the request?
    No. `when()` is a readability method that returns the same request specification. The HTTP call is made by the verb method that follows it — `get`, `post`, `put`, `patch`, `delete`, `head` or `options`. You can legally write `given()...get("/users")` with no `when()` at all; the stage exists purely so the chain reads like a specification.
  • Where do the matchers in body("name", equalTo("ann")) come from, and where does the "name" expression come from?
    The matcher is a Hamcrest `Matcher`, so anything Hamcrest offers works — `equalTo`, `hasItems`, `containsString`, `greaterThan`, plus composed matchers. The path string is a GPath expression evaluated by Groovy against the parsed body, which is why nested access reads as `user.address.city` and collection operations like `items.size()` or `items.findAll { it.active }` are available.
  • Does REST Assured start your application for you?
    No. It is only an HTTP client with assertions, so a server must already be listening at the configured base URI and port — started by the surrounding test setup, a container, or a deployed environment. If nothing is listening you get a connection-refused exception, not a failed assertion.

It is like dictating a request to an assistant: 'given these headers and this body, when you POST it, then check the receipt says 201 and the name is ann.'

saying these in an interview costs you the question

  • Claiming when() is what performs the HTTP call, rather than the verb method after it.
  • Thinking REST Assured starts or embeds the application under test.
  • Believing the body() path is JSONPath ($.user.name) — it is GPath, so it is user.name with no leading $.
  • Setting RestAssured.baseURI/port inside individual tests and never resetting, then blaming flakiness on the library.
  • Saying it only works for JSON — it also handles XML, form and multipart payloads.

context

open as a page

How do you assert on a JSON response body in REST Assured — including nested fields, arrays and "the whole shape" of the payload?

level: middleimportance: must knowfreq 58%

basics

~20 s

Use then().body(path, matcher). The path is a GPath expression — user.address.city, items[0].id, items.size(), items.name — and the matcher is Hamcrest (equalTo, hasItems, containsInAnyOrder). Use rootPath to avoid repeating a prefix, and matchesJsonSchemaInClasspath for whole-payload shape.

open as a page

How does REST Assured let you authenticate requests, and what is the difference between its basic() and preemptive().basic() authentication options?

level: middleimportance: should knowfreq 40%

basics

~20 s

given().auth() offers basic, preemptive basic, digest, form, OAuth 1/2 and certificate auth. preemptive().basic() sends the Authorization header on the first request; plain basic() waits for a 401 challenge before resending with credentials — so it costs an extra round trip and fails against servers that never challenge.

open as a page

With REST Assured, how do you pull values out of a response — for example to reuse an id created by one API call in the next request — and how do you turn a response body into a typed object?

level: middleimportance: should knowfreq 48%

basics

~20 s

End the chain with .extract(). Use extract().path("id") for a single GPath value, extract().response() for the whole Response (status, headers, body, time), or extract().as(User.class) to deserialize into a POJO. Then pass the value into the next given().

open as a page

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?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Build 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.

open as a page

You inherit a REST Assured API suite of several hundred tests that runs against a deployed service: it takes 40 minutes, roughly one run in three fails for reasons nobody trusts, and any API change turns dozens of tests red. How would you approach making it trustworthy?

level: principalimportance: nice to knowfreq 24%

basics

~20 s

Measure first: classify failures into real bugs, shared-environment/data collisions, timing, and over-assertion. Then fix causes — per-test data created and cleaned up via the API, no shared mutable state or ordering, parallel execution with per-request specs, assertions narrowed to behaviour plus one schema check, and failure-only logging with correlation ids for traceability.

open as a page