restassured-testing
Authors REST Assured (Java) API tests using the given().when().then() BDD-style DSL - status code + JSON/XML path assertions + authentication (Basic, OAuth2, API key). Configures Maven / Gradle dependencies, runs via JUnit 5, and emits Surefire / JaCoCo reports for CI gating. Use when the project is on the JVM and the team wants type-safe API tests in the same language as the application.
restassured-testing
Overview
REST Assured is a Java DSL for HTTP API testing with a BDD-style given().when().then() chain (restassured-readme). It runs inside any JUnit / TestNG suite and consumes JSON, XML, and other content types via path expressions backed by Hamcrest matchers (restassured-usage).
This is the JVM-native counterpart to postman-collections. Use REST Assured when the team's primary stack is Java / Kotlin / Scala and type-safe authoring + same-language refactoring matter.
When to use
If the team is non-JVM, evaluate postman-collections (JSON-driven), tavern-testing (Python YAML), or karate-testing (cross-platform DSL).
Install
Maven
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>6.0.0</version>
<scope>test</scope>
</dependency>(Per restassured-usage.)
For JSON Schema validation, add json-schema-validator from the same group; for path-style assertions on JSON, the core dependency is enough.
Gradle
testImplementation 'io.rest-assured:rest-assured:6.0.0'Authoring
Imports
The canonical static imports for fluent authoring (restassured-usage):
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
import static io.restassured.matcher.RestAssuredMatchers.*;These bring given(), get(), post(), etc. plus Hamcrest matchers (equalTo, hasItems, containsString, nullValue, etc.) into scope.
given() / when() / then() chain
The basic shape (restassured-readme):
given().
param("key1", "value1").
when().
post("/somewhere").
then().
body(containsString("OK"));given() configures the request (params, headers, body, auth); when() issues the HTTP verb; then() asserts the response.
Status code assertion
get("/x").then().assertThat().statusCode(200);(Per restassured-usage.)
statusCode(int) is equivalent to statusCode(equalTo(int)). For ranges, use statusCode(allOf(greaterThanOrEqualTo(200), lessThan(300))).
JSON path assertions
get("/lotto").then().body("lotto.lottoId", equalTo(5));
get("/lotto").then().body("lotto.winners.winnerId", hasItems(23, 54));(Per restassured-usage.)
The first argument is a Groovy-style JSON path (models[0].author.name); the second is a Hamcrest matcher.
XML path assertions
given().parameters("firstName", "John", "lastName", "Doe").
when().post("/greetXML").
then().body("greeting.firstName", equalTo("John"));(Per restassured-usage.)
Combined status + body assertion
given().header("Accept", "application/json").
when().get("/orders/42").
then().
statusCode(200).
body("order_id", equalTo(42)).
body("status", equalTo("shipped")).
body("items", hasSize(greaterThan(0)));Authentication
REST Assured ships first-class auth support (restassured-usage):
OAuth2 / Bearer token
given().auth().oauth2(accessToken).when().get("/secured").then().statusCode(200);Preemptive Basic Auth
given().auth().preemptive().basic("username", "password").
when().get("/secured/hello").
then().statusCode(200);preemptive() sends the Authorization header on the first request without waiting for a 401 challenge - required for most modern APIs that don't issue WWW-Authenticate.
API Key in header
given().header("X-API-KEY", "your-api-key").when().get("/endpoint");For any custom auth scheme, fall through to the header() form - REST Assured doesn't impose a structure beyond the standard Basic / OAuth1/2 patterns.
Worked example: full JUnit 5 test
package com.example.api;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
class OrdersApiIT {
@BeforeAll
static void setup() {
RestAssured.baseURI = System.getProperty("api.baseURI", "http://localhost:8080");
}
@Test
@DisplayName("GET /orders/42 returns the expected order")
void getOrder() {
given().
auth().oauth2(System.getenv("API_TOKEN")).
accept(ContentType.JSON).
when().
get("/orders/42").
then().
statusCode(200).
contentType(ContentType.JSON).
body("order_id", equalTo(42)).
body("status", isOneOf("placed", "shipped", "completed", "returned")).
body("items", hasSize(greaterThan(0))).
body("items[0].sku", matchesPattern("^[A-Z0-9]{8}$"));
}
}CI integration
# .github/workflows/api-tests.yml
name: api-tests
on:
pull_request:
push:
branches: [main]
jobs:
restassured:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
- name: Run integration tests
env:
API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}
run: |
mvn -B verify \
-Dapi.baseURI=https://staging.example.com \
-Dfailsafe.includes='**/*IT.java'
- name: Surface JUnit results
if: always()
uses: dorny/test-reporter@v1
with:
name: REST Assured tests
path: target/failsafe-reports/TEST-*.xml
reporter: java-junit
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: failsafe-reports
path: target/failsafe-reports/
retention-days: 14The *IT.java suffix is the Maven Failsafe convention for integration tests (separate from *Test.java unit tests). Failsafe uploads JUnit XML to target/failsafe-reports/ automatically - same format as Newman's --reporter-junit-export.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Hard-coded RestAssured.baseURI in source | Tests bind to one environment; can't run against staging vs local. | Read from system property: RestAssured.baseURI = System.getProperty("api.baseURI", default);. |
| Inline tokens in source | Secrets leak; PRs flag; rotation pain. | Read from env var: System.getenv("API_TOKEN"). |
.basic() without .preemptive() | Sends an unauthorized request first; doubles round trips and may fail on APIs that don't issue WWW-Authenticate. | Use auth().preemptive().basic(...). |
| Asserting only status code without body shape | Status 200 with garbage body still passes. | Always assert at least one body field, ideally schema-validated. |
One mega-assertion body(equalTo(jsonString)) | Diff is uninterpretable on failure. | Multiple field-level body(path, matcher) calls; fail-fast naming surfaces which field is wrong. |
Mixing unit-test (*Test.java) and integration (*IT.java) by name only | Maven Surefire vs Failsafe gets confused; integration tests run during the unit-test phase. | Strict naming: *Test.java for unit (mvn test); *IT.java for integration (mvn verify). |