REST Assured
REST Assured is a Java DSL for testing HTTP APIs that reads like the behavior it describes: given some request setup, when you call an endpoint, then assert the response. It sends real HTTP requests, so it pairs naturally with @SpringBootTest(RANDOM_PORT) for expressive end-to-end API tests.
Dependency
REST Assured is not part of spring-boot-starter-test, so add it explicitly with test scope.
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
Note: Spring Boot manages the REST Assured version in its dependency BOM, so you can omit the
<version>— the curated version is wired in automatically.
Pointing REST Assured at the running app
Start the server on a random port, then tell REST Assured the port in @BeforeEach.
import io.restassured.RestAssured;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class ProductApiRestAssuredTest {
@LocalServerPort
int port;
@BeforeEach
void setUp() {
RestAssured.baseURI = "http://localhost";
RestAssured.port = port;
}
}
The given/when/then DSL
Each test reads top to bottom: configure the request, fire it, assert the response.
@Test
void returnsProductById() {
given()
.accept(ContentType.JSON)
.when()
.get("/api/products/1")
.then()
.statusCode(200)
.body("name", equalTo("Keyboard"))
.body("price", equalTo(49.90f));
}
Output:
ProductApiRestAssuredTest > returnsProductById() PASSED
POSTing a JSON body
Set the content type and pass a payload — REST Assured serializes objects with Jackson.
@Test
void createsProduct() {
given()
.contentType(ContentType.JSON)
.body("""
{ "name": "Mouse", "price": 19.90 }
""")
.when()
.post("/api/products")
.then()
.statusCode(201)
.header("Location", containsString("/api/products/"))
.body("id", notNullValue())
.body("name", equalTo("Mouse"));
}
Body and jsonPath assertions
The body(...) matchers use a GPath/JsonPath-style expression and Hamcrest matchers. They handle nested fields and collections cleanly.
@Test
void listReturnsAllProducts() {
given()
.when()
.get("/api/products")
.then()
.statusCode(200)
.body("size()", equalTo(2))
.body("name", hasItems("Keyboard", "Mouse"))
.body("[0].price", greaterThan(0f));
}
To pull a value out of the response for further assertions, extract it:
long id = given()
.contentType(ContentType.JSON)
.body(new ProductRequest("Pad", new BigDecimal("9.90")))
.when()
.post("/api/products")
.then()
.statusCode(201)
.extract().jsonPath().getLong("id");
assertThat(id).isPositive();
Sending auth headers
Attach bearer tokens, basic auth, or custom headers in the given() block — exactly what you need for secured endpoints.
@Test
void rejectsRequestWithoutToken() {
given()
.when()
.get("/api/admin/users")
.then()
.statusCode(401);
}
@Test
void allowsRequestWithToken() {
given()
.header("Authorization", "Bearer " + validJwt)
.when()
.get("/api/admin/users")
.then()
.statusCode(200);
// Built-in auth helpers also exist:
given().auth().preemptive().basic("admin", "secret");
given().auth().oauth2(validJwt);
}
REST Assured vs MockMvc / TestRestTemplate
All three test HTTP endpoints, but at different levels and with different ergonomics.
| Tool | Real HTTP? | Style | Best for |
|---|---|---|---|
MockMvc (@WebMvcTest) | No (mock servlet) | perform().andExpect() | Fast controller slice tests |
TestRestTemplate | Yes (RANDOM_PORT) | Imperative, returns ResponseEntity | Plain integration tests |
| REST Assured | Yes (RANDOM_PORT) | Fluent given/when/then | Readable end-to-end API specs |
Tip: Use
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails()once in setup so failing tests print the full request and response — it turns a cryptic matcher failure into an obvious diagnosis.
Warning: REST Assured drives the API over the network, so it is slower than
MockMvc. Keep the bulk of your controller coverage in@WebMvcTestand use REST Assured for a focused set of full-stack acceptance tests.