Introduction
In modern software development, APIs (Application Programming Interfaces) are the backbone of virtually every application. Whether you are building a mobile app, a microservice architecture, or a web platform, the reliability of your APIs directly determines the quality of your product.
But how do we ensure our APIs actually work as expected — before users find out they don't?
That's where API Testing Frameworks come in. According to the widely referenced article "Top 10 API Testing Tools in 2020" published on Medium, the industry has converged on a set of powerful tools to automate this process. Among them, REST Assured consistently ranks as one of the top choices for Java-based teams.
In this article, you will learn:
- What REST Assured is and why it matters
- How to set it up from scratch
- How to write real-world API tests against a live REST API
- Best practices to make your tests maintainable and scalable
What Is REST Assured?
REST Assured is an open-source Java library that simplifies testing and validating REST APIs. Created by Johan Haleby and now maintained by the community, it provides a domain-specific language (DSL) that reads almost like plain English, making tests both powerful and easy to understand.
Why REST Assured?
| Feature | Benefit |
|---|---|
BDD-style syntax (given/when/then) |
Tests are readable by non-developers |
| Native JSON & XML parsing | No manual deserialization needed |
| Full HTTP support (GET, POST, PUT, DELETE, PATCH) | Covers all REST operations |
| Seamless JUnit/TestNG integration | Works inside your existing CI/CD pipeline |
| Schema validation | Validate JSON structure automatically |
| OAuth2 / JWT support | Ready for secured APIs |
Project Setup
Prerequisites
- Java 11+
- Maven or Gradle
- An IDE (IntelliJ IDEA or Eclipse)
Maven Dependencies (pom.xml)
<dependencies>
<!-- REST Assured core -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<!-- JSON Schema Validator -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<!-- Hamcrest matchers -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
</dependencies>
Project Structure
src/
└── test/
└── java/
└── com/
└── apitesting/
├── config/
│ └── BaseTest.java ← shared configuration
├── tests/
│ ├── GetUsersTest.java
│ ├── CreateUserTest.java
│ ├── UpdateUserTest.java
│ └── DeleteUserTest.java
└── models/
└── User.java ← POJO for deserialization
The Target API
For all examples in this article, we will test against Reqres — a free, public REST API specifically designed for testing and prototyping. It supports GET, POST, PUT, PATCH, and DELETE operations without requiring authentication.
Base URL: https://reqres.in/api
Real-World Code Examples
1. Base Configuration (BaseTest.java)
Setting up a centralized configuration avoids repeating the base URL and common headers across every test file.
package com.apitesting.config;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import org.junit.jupiter.api.BeforeAll;
import static org.hamcrest.Matchers.lessThan;
public class BaseTest {
protected static RequestSpecification requestSpec;
protected static ResponseSpecification responseSpec;
@BeforeAll
public static void setup() {
// Base request specification — shared by all tests
requestSpec = new RequestSpecBuilder()
.setBaseUri("https://reqres.in/api")
.setContentType(ContentType.JSON)
.addHeader("Accept", "application/json")
.build();
// Base response specification — all responses should reply under 3 seconds
responseSpec = new ResponseSpecBuilder()
.expectResponseTime(lessThan(3000L))
.build();
// Enable request/response logging for failed tests
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
}
}
2. GET Request — Retrieve a List of Users
This test validates that the API returns a paginated list of users with the correct structure.
package com.apitesting.tests;
import com.apitesting.config.BaseTest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
public class GetUsersTest extends BaseTest {
@Test
@DisplayName("GET /users - should return paginated list with correct structure")
public void shouldReturnPaginatedUsersList() {
given()
.spec(requestSpec)
.queryParam("page", 2)
.when()
.get("/users")
.then()
.spec(responseSpec)
.statusCode(200)
.body("page", equalTo(2))
.body("per_page", equalTo(6))
.body("data", hasSize(6))
.body("data[0].id", equalTo(7))
.body("data[0].email", containsString("@reqres.in"))
.body("data.first_name", everyItem(notNullValue()));
}
@Test
@DisplayName("GET /users/{id} - should return single user by ID")
public void shouldReturnSingleUserById() {
int userId = 2;
given()
.spec(requestSpec)
.when()
.get("/users/{id}", userId)
.then()
.spec(responseSpec)
.statusCode(200)
.body("data.id", equalTo(userId))
.body("data.email", equalTo("janet.weaver@reqres.in"))
.body("data.first_name", equalTo("Janet"))
.body("data.last_name", equalTo("Weaver"));
}
@Test
@DisplayName("GET /users/{id} - should return 404 for non-existent user")
public void shouldReturn404ForNonExistentUser() {
given()
.spec(requestSpec)
.when()
.get("/users/{id}", 9999)
.then()
.statusCode(404)
.body(equalTo("{}"));
}
}
Running this test produces output like:
✅ GET /users - should return paginated list with correct structure PASSED (342ms)
✅ GET /users/{id} - should return single user by ID PASSED (198ms)
✅ GET /users/{id} - should return 404 for non-existent user PASSED (201ms)
3. POST Request — Create a New User
Testing resource creation requires validating the response body, status code (201), and that the server assigns an ID.
package com.apitesting.tests;
import com.apitesting.config.BaseTest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
public class CreateUserTest extends BaseTest {
@Test
@DisplayName("POST /users - should create a new user and return 201")
public void shouldCreateNewUser() {
String requestBody = """
{
"name": "Mariela García",
"job": "QA Engineer"
}
""";
given()
.spec(requestSpec)
.body(requestBody)
.when()
.post("/users")
.then()
.spec(responseSpec)
.statusCode(201)
.body("name", equalTo("Mariela García"))
.body("job", equalTo("QA Engineer"))
.body("id", notNullValue()) // server assigns an ID
.body("createdAt", notNullValue()); // server sets a timestamp
}
@Test
@DisplayName("POST /users - should return id as a string")
public void shouldReturnIdAsString() {
String requestBody = """
{
"name": "Test User",
"job": "Developer"
}
""";
String createdId =
given()
.spec(requestSpec)
.body(requestBody)
.when()
.post("/users")
.then()
.statusCode(201)
.extract()
.path("id"); // Extract the generated ID for chaining
System.out.println("✅ User created with ID: " + createdId);
// Can now use this ID in a follow-up GET request
}
}
4. PUT Request — Full Update of a Resource
package com.apitesting.tests;
import com.apitesting.config.BaseTest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
public class UpdateUserTest extends BaseTest {
@Test
@DisplayName("PUT /users/{id} - should fully update user and return 200")
public void shouldFullyUpdateUser() {
int userId = 2;
String updateBody = """
{
"name": "Mariela Updated",
"job": "Senior QA Engineer"
}
""";
given()
.spec(requestSpec)
.body(updateBody)
.when()
.put("/users/{id}", userId)
.then()
.spec(responseSpec)
.statusCode(200)
.body("name", equalTo("Mariela Updated"))
.body("job", equalTo("Senior QA Engineer"))
.body("updatedAt", notNullValue());
}
@Test
@DisplayName("PATCH /users/{id} - should partially update user")
public void shouldPartiallyUpdateUser() {
int userId = 2;
String patchBody = """
{
"job": "Lead QA Engineer"
}
""";
given()
.spec(requestSpec)
.body(patchBody)
.when()
.patch("/users/{id}", userId)
.then()
.spec(responseSpec)
.statusCode(200)
.body("job", equalTo("Lead QA Engineer"))
.body("updatedAt", notNullValue());
}
}
5. DELETE Request — Remove a Resource
package com.apitesting.tests;
import com.apitesting.config.BaseTest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
public class DeleteUserTest extends BaseTest {
@Test
@DisplayName("DELETE /users/{id} - should return 204 No Content")
public void shouldDeleteUser() {
int userId = 2;
given()
.spec(requestSpec)
.when()
.delete("/users/{id}", userId)
.then()
.statusCode(204);
// 204 means success with no response body — exactly what REST expects
}
}
6. Advanced: JSON Schema Validation
One of REST Assured's most powerful features is JSON Schema validation — ensuring the API response matches a predefined structure contract.
Schema file (src/test/resources/schemas/users_schema.json):
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["page", "per_page", "total", "total_pages", "data"],
"properties": {
"page": { "type": "integer" },
"per_page": { "type": "integer" },
"total": { "type": "integer" },
"total_pages": { "type": "integer" },
"data": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "email", "first_name", "last_name", "avatar"],
"properties": {
"id": { "type": "integer" },
"email": { "type": "string", "format": "email" },
"first_name": { "type": "string" },
"last_name": { "type": "string" },
"avatar": { "type": "string", "format": "uri" }
}
}
}
}
}
Test using the schema:
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
@Test
@DisplayName("GET /users - response must match JSON schema contract")
public void shouldMatchJsonSchema() {
given()
.spec(requestSpec)
.queryParam("page", 1)
.when()
.get("/users")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/users_schema.json"));
}
This kind of test is invaluable in a microservices environment — it catches breaking changes in the API contract before they reach production.
7. Authentication — Testing a Secured Endpoint
Real APIs require authentication. REST Assured handles this elegantly:
@Test
@DisplayName("POST /login - should authenticate and return token")
public void shouldAuthenticateAndReturnToken() {
String credentials = """
{
"email": "eve.holt@reqres.in",
"password": "cityslicka"
}
""";
// Step 1: Authenticate and extract token
String token =
given()
.spec(requestSpec)
.body(credentials)
.when()
.post("/login")
.then()
.statusCode(200)
.body("token", notNullValue())
.extract()
.path("token");
System.out.println("Token received: " + token);
// Step 2: Use token in a subsequent request
given()
.spec(requestSpec)
.header("Authorization", "Bearer " + token)
.when()
.get("/users")
.then()
.statusCode(200);
}
@Test
@DisplayName("POST /login - should return 400 for missing password")
public void shouldReturn400ForMissingPassword() {
String badCredentials = """
{
"email": "eve.holt@reqres.in"
}
""";
given()
.spec(requestSpec)
.body(badCredentials)
.when()
.post("/login")
.then()
.statusCode(400)
.body("error", equalTo("Missing password"));
}
Running the Tests
From the command line (Maven)
# Run all tests
mvn test
# Run a specific test class
mvn test -Dtest=GetUsersTest
# Run with detailed output
mvn test -Dtest=GetUsersTest -pl . --also-make
Expected Console Output
[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0
✅ shouldReturnPaginatedUsersList PASSED (342ms)
✅ shouldReturnSingleUserById PASSED (198ms)
✅ shouldReturn404ForNonExistentUser PASSED (201ms)
✅ shouldCreateNewUser PASSED (415ms)
✅ shouldReturnIdAsString PASSED (380ms)
✅ shouldFullyUpdateUser PASSED (265ms)
✅ shouldDeleteUser PASSED (188ms)
✅ shouldMatchJsonSchema PASSED (320ms)
[INFO] BUILD SUCCESS
Best Practices
1. Use Specification Builders
Never hardcode base URLs or headers. Use RequestSpecBuilder and ResponseSpecBuilder to share configuration across all tests (as shown in BaseTest.java).
2. Separate Test Data from Test Logic
Use POJOs or external JSON files for request/response bodies — never inline hardcoded strings in complex scenarios.
// ❌ Avoid
.body("{ \"name\": \"Mariela\", \"job\": \"QA\" }")
// ✅ Prefer — use a POJO
User user = new User("Mariela", "QA Engineer");
given().body(user).when().post("/users")...
3. Validate Both Happy Path and Error Scenarios
Always test what happens when things go wrong — missing fields, invalid IDs, unauthorized requests. Error cases reveal more bugs than success cases.
4. Assert Response Time
Slow APIs degrade user experience. Always include a response time assertion in your base spec:
.expectResponseTime(lessThan(2000L)) // Fail if response > 2 seconds
5. Integrate into CI/CD
REST Assured tests are just JUnit tests — add them to your GitHub Actions or Jenkins pipeline:
# .github/workflows/api-tests.yml
- name: Run API Tests
run: mvn test
Conclusion
REST Assured is one of the most mature and expressive API testing frameworks available today. Its BDD-style syntax makes tests readable for the entire team, while its deep Java integration means it fits naturally into enterprise development workflows.
In this article, we covered:
- ✅ Project setup with Maven
- ✅ Base configuration with shared specifications
- ✅ Real-world tests: GET, POST, PUT, PATCH, DELETE
- ✅ JSON Schema validation for contract testing
- ✅ Authentication flows with token extraction
- ✅ Best practices for maintainable test suites
By applying these patterns, your team can catch API regressions before they ever reach production — saving time, reducing bugs, and building confidence in every deployment.
References
- Moataz Eldebsy. Top 10 API Testing Tools in 2020 (Detailed Reviews). Medium. https://medium.com/@moatazeldebsy/top-10-api-testing-tools-in-2020-detailed-reviews-7b2e28e29c5a
- REST Assured Official Documentation. https://rest-assured.io/
- REST Assured GitHub Repository. https://github.com/rest-assured/rest-assured
- Reqres — Fake REST API for Testing. https://reqres.in
- JUnit 5 User Guide. https://junit.org/junit5/docs/current/user-guide/
- JSON Schema Specification. https://json-schema.org/
© 2026 Mariela — Software Quality Engineering
Top comments (0)