From qa-api-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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-api-testing:restassured-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
REST Assured is a Java DSL for HTTP API testing with a BDD-style
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.
If the team is non-JVM, evaluate
postman-collections (JSON-driven),
tavern-testing (Python YAML), or
karate-testing (cross-platform DSL).
<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.
testImplementation 'io.rest-assured:rest-assured:6.0.0'
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.
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.
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))).
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.
given().parameters("firstName", "John", "lastName", "Doe").
when().post("/greetXML").
then().body("greeting.firstName", equalTo("John"));
(Per restassured-usage.)
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)));
REST Assured ships first-class auth support (restassured-usage):
given().auth().oauth2(accessToken).when().get("/secured").then().statusCode(200);
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.
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.
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}$"));
}
}
# .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: 14
The *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-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). |
@Execution(CONCURRENT)
or TestNG's parallel="methods" to parallelize at the framework
level.io.rest-assured:json-schema-validator if you want
body(matchesJsonSchema(file)) style assertions.postman-collections -
JSON-driven counterpart for non-JVM teams.schemathesis-fuzzing -
property-based fuzzing complement (different approach to coverage).npx claudepluginhub testland/qa --plugin qa-api-testingProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.