Introduction to REST-assured
REST-assured is a Java-based domain-specific language (DSL) that simplifies testing and validation of REST web services. Created by Johan Haleby, it has become the de facto standard for API testing in Java ecosystems due to its intuitive syntax and powerful features.
Why Choose REST-assured?
Key Advantages
Fluent Interface: REST-assured uses a behavior-driven development (BDD) style syntax that makes tests readable and maintainable:
given().param("key", "value").when().get("/endpoint").then().statusCode(200);
Built-in Assertions: Comprehensive assertion capabilities using Hamcrest matchers, eliminating the need for external assertion libraries.
JSON/XML Support: Native support for parsing and validating JSON and XML responses with JsonPath and XmlPath.
Authentication Handling: Built-in support for various authentication mechanisms including OAuth, Basic Auth, and custom headers.
Integration Friendly: Seamlessly integrates with popular testing frameworks like TestNG, JUnit, and Maven/Gradle build systems.
REST-assured Architecture and Components
Core Components
- RequestSpecification: Defines request parameters, headers, and body
- Response: Captures and validates API responses
- ValidatableResponse: Provides fluent validation methods
- JsonPath/XmlPath: Extract and validate data from responses
- RequestSpecBuilder/ResponseSpecBuilder: Create reusable specifications
The Given-When-Then Pattern
REST-assured follows the BDD pattern:
- Given: Set up request specifications (headers, parameters, body)
- When: Execute the HTTP request (GET, POST, PUT, DELETE)
- Then: Validate the response (status codes, headers, body content)
Complete Implementation Examples
Project Setup
<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>api-testing-framework</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<rest-assured.version>5.3.2</rest-assured.version>
<testng.version>7.8.0</testng.version>
</properties>
<dependencies>
<!-- REST-assured core -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
</dependency>
<!-- JSON Schema validation -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
</dependency>
<!-- TestNG framework -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
<!-- JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
</project>
Configuration and Base Test Class
package com.example.framework;
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.testng.annotations.BeforeClass;
public class BaseApiTest {
protected static RequestSpecification requestSpec;
protected static ResponseSpecification responseSpec;
protected static final String BASE_URL = "https://jsonplaceholder.typicode.com";
protected static final String REQRES_URL = "https://reqres.in/api";
@BeforeClass
public void setupBaseConfiguration() {
// Global REST-assured configuration
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
// Build request specification
requestSpec = new RequestSpecBuilder()
.setBaseUri(BASE_URL)
.setContentType(ContentType.JSON)
.addHeader("User-Agent", "API-Testing-Framework/1.0")
.build();
// Build response specification
responseSpec = new ResponseSpecBuilder()
.expectResponseTime(lessThan(5000L))
.build();
// Set global specifications
RestAssured.requestSpecification = requestSpec;
RestAssured.responseSpecification = responseSpec;
}
}
Example 1: E-commerce API Testing Suite
package com.example.tests;
import com.example.framework.BaseApiTest;
import io.restassured.response.Response;
import org.testng.annotations.Test;
import org.testng.annotations.DataProvider;
import static io.restassured.RestAssured.*;
import static io.restassured.matcher.RestAssuredMatchers.*;
import static org.hamcrest.Matchers.*;
public class EcommerceApiTests extends BaseApiTest {
private int createdProductId;
private String authToken = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
@Test(priority = 1)
public void testGetAllProducts() {
given()
.spec(requestSpec)
.header("Authorization", authToken)
.when()
.get("/products")
.then()
.spec(responseSpec)
.statusCode(200)
.body("size()", greaterThan(0))
.body("[0]", hasKey("id"))
.body("[0]", hasKey("title"))
.body("[0]", hasKey("price"))
.body("[0].id", instanceOf(Integer.class))
.body("findAll { it.price > 0 }.size()", greaterThan(0));
}
@Test(priority = 2)
public void testGetProductById() {
int productId = 1;
Response response = given()
.spec(requestSpec)
.pathParam("id", productId)
.when()
.get("/products/{id}")
.then()
.spec(responseSpec)
.statusCode(200)
.body("id", equalTo(productId))
.body("title", notNullValue())
.body("price", greaterThan(0f))
.body("category", notNullValue())
.extract().response();
// Extract and validate specific fields
String title = response.path("title");
Float price = response.path("price");
System.out.println("Product: " + title + " - Price: $" + price);
}
@Test(priority = 3)
public void testCreateProduct() {
String productJson = """
{
"title": "Test Product",
"price": 29.99,
"description": "A test product for API testing",
"category": "electronics",
"image": "https://example.com/image.jpg"
}
""";
Response response = given()
.spec(requestSpec)
.header("Authorization", authToken)
.body(productJson)
.when()
.post("/products")
.then()
.spec(responseSpec)
.statusCode(201)
.body("title", equalTo("Test Product"))
.body("price", equalTo(29.99f))
.body("id", notNullValue())
.extract().response();
createdProductId = response.path("id");
System.out.println("Created product with ID: " + createdProductId);
}
@Test(priority = 4, dependsOnMethods = "testCreateProduct")
public void testUpdateProduct() {
String updatedProductJson = """
{
"title": "Updated Test Product",
"price": 39.99,
"description": "An updated test product",
"category": "electronics",
"image": "https://example.com/updated-image.jpg"
}
""";
given()
.spec(requestSpec)
.header("Authorization", authToken)
.pathParam("id", createdProductId)
.body(updatedProductJson)
.when()
.put("/products/{id}")
.then()
.spec(responseSpec)
.statusCode(200)
.body("title", equalTo("Updated Test Product"))
.body("price", equalTo(39.99f))
.body("id", equalTo(createdProductId));
}
@Test(priority = 5, dependsOnMethods = "testUpdateProduct")
public void testDeleteProduct() {
given()
.spec(requestSpec)
.header("Authorization", authToken)
.pathParam("id", createdProductId)
.when()
.delete("/products/{id}")
.then()
.statusCode(200);
// Verify product is deleted
given()
.spec(requestSpec)
.pathParam("id", createdProductId)
.when()
.get("/products/{id}")
.then()
.statusCode(404);
}
@DataProvider(name = "invalidProductData")
public Object[][] invalidProductData() {
return new Object[][] {
{"", 29.99, "Empty title should fail"},
{"Valid Title", -10.0, "Negative price should fail"},
{"Valid Title", 0, "Zero price should fail"}
};
}
@Test(dataProvider = "invalidProductData")
public void testCreateProductWithInvalidData(String title, double price, String description) {
String invalidProductJson = String.format("""
{
"title": "%s",
"price": %.2f,
"description": "%s",
"category": "electronics"
}
""", title, price, description);
given()
.spec(requestSpec)
.header("Authorization", authToken)
.body(invalidProductJson)
.when()
.post("/products")
.then()
.statusCode(anyOf(equalTo(400), equalTo(422)))
.body("error", notNullValue());
}
}
Example 2: User Authentication and Profile Management
package com.example.tests;
import com.example.framework.BaseApiTest;
import io.restassured.response.Response;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeClass;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class UserAuthenticationTests extends BaseApiTest {
private String userToken;
private int userId;
@BeforeClass
public void setupUserTests() {
// Override base URL for this test class
RestAssured.baseURI = REQRES_URL;
}
@Test(priority = 1)
public void testUserRegistration() {
String registrationData = """
{
"email": "eve.holt@reqres.in",
"password": "pistol"
}
""";
Response response = given()
.contentType("application/json")
.body(registrationData)
.when()
.post("/register")
.then()
.statusCode(200)
.body("id", notNullValue())
.body("token", notNullValue())
.extract().response();
userId = response.path("id");
userToken = response.path("token");
System.out.println("Registered user ID: " + userId);
System.out.println("Received token: " + userToken);
}
@Test(priority = 2)
public void testUserLogin() {
String loginData = """
{
"email": "eve.holt@reqres.in",
"password": "cityslicka"
}
""";
given()
.contentType("application/json")
.body(loginData)
.when()
.post("/login")
.then()
.statusCode(200)
.body("token", notNullValue())
.body("token", hasLength(greaterThan(10)));
}
@Test(priority = 3)
public void testGetUserProfile() {
given()
.pathParam("id", 2)
.when()
.get("/users/{id}")
.then()
.statusCode(200)
.body("data.id", equalTo(2))
.body("data.email", containsString("@"))
.body("data.first_name", notNullValue())
.body("data.last_name", notNullValue())
.body("data.avatar", startsWith("https://"))
.body("support", hasKey("url"))
.body("support", hasKey("text"));
}
@Test(priority = 4)
public void testUpdateUserProfile() {
String updateData = """
{
"name": "morpheus",
"job": "zion resident"
}
""";
given()
.contentType("application/json")
.pathParam("id", 2)
.body(updateData)
.when()
.put("/users/{id}")
.then()
.statusCode(200)
.body("name", equalTo("morpheus"))
.body("job", equalTo("zion resident"))
.body("updatedAt", notNullValue());
}
@Test
public void testGetUsersList() {
given()
.queryParam("page", 2)
.queryParam("per_page", 6)
.when()
.get("/users")
.then()
.statusCode(200)
.body("page", equalTo(2))
.body("per_page", equalTo(6))
.body("data.size()", lessThanOrEqualTo(6))
.body("data[0]", hasKey("id"))
.body("data[0]", hasKey("email"))
.body("data.findAll { it.email.contains('@') }.size()",
equalTo(response().path("data.size()")));
}
@Test
public void testInvalidLoginAttempt() {
String invalidLoginData = """
{
"email": "peter@klaven"
}
""";
given()
.contentType("application/json")
.body(invalidLoginData)
.when()
.post("/login")
.then()
.statusCode(400)
.body("error", equalTo("Missing password"));
}
}
Example 3: Advanced Features and Utilities
package com.example.utils;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import static io.restassured.RestAssured.*;
import static io.restassured.module.jsv.JsonSchemaValidator.*;
public class ApiTestUtils {
/**
* Generic method to perform API requests with logging
*/
public static Response performRequest(String method, String endpoint,
Object requestBody, String authToken) {
RequestSpecification request = given()
.contentType("application/json")
.log().all();
if (authToken != null) {
request.header("Authorization", "Bearer " + authToken);
}
if (requestBody != null) {
request.body(requestBody);
}
Response response;
switch (method.toUpperCase()) {
case "GET":
response = request.when().get(endpoint);
break;
case "POST":
response = request.when().post(endpoint);
break;
case "PUT":
response = request.when().put(endpoint);
break;
case "DELETE":
response = request.when().delete(endpoint);
break;
default:
throw new IllegalArgumentException("Unsupported HTTP method: " + method);
}
response.then().log().all();
return response;
}
/**
* Extract all field values from JSON response
*/
public static void printAllResponseFields(Response response) {
System.out.println("Response Headers:");
response.getHeaders().forEach(header ->
System.out.println(header.getName() + ": " + header.getValue()));
System.out.println("\nResponse Body:");
System.out.println(response.getBody().asPrettyString());
}
/**
* Validate response against JSON schema
*/
public static void validateJsonSchema(Response response, String schemaPath) {
response.then().assertThat()
.body(matchesJsonSchemaInClasspath(schemaPath));
}
}
Example 4: JSON Schema Validation
package com.example.tests;
import com.example.framework.BaseApiTest;
import com.example.utils.ApiTestUtils;
import io.restassured.response.Response;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.*;
import static io.restassured.module.jsv.JsonSchemaValidator.*;
public class JsonSchemaValidationTests extends BaseApiTest {
@Test
public void testUserResponseSchema() {
Response response = given()
.spec(requestSpec)
.when()
.get("/users/1")
.then()
.statusCode(200)
.extract().response();
// Validate against JSON schema
response.then().assertThat()
.body(matchesJsonSchemaInClasspath("schemas/user-schema.json"));
}
@Test
public void testProductsListSchema() {
given()
.spec(requestSpec)
.when()
.get("/products")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/products-list-schema.json"));
}
}
JSON Schema Files
// src/test/resources/schemas/user-schema.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"username": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"address": {
"type": "object",
"properties": {
"street": {"type": "string"},
"suite": {"type": "string"},
"city": {"type": "string"},
"zipcode": {"type": "string"}
},
"required": ["street", "city", "zipcode"]
},
"phone": {
"type": "string"
},
"website": {
"type": "string"
}
},
"required": ["id", "name", "email"]
}
Example 5: Performance and Load Testing
package com.example.tests;
import com.example.framework.BaseApiTest;
import org.testng.annotations.Test;
import java.util.concurrent.TimeUnit;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class PerformanceTests extends BaseApiTest {
@Test
public void testApiResponseTime() {
given()
.spec(requestSpec)
.when()
.get("/users")
.then()
.statusCode(200)
.time(lessThan(2000L), TimeUnit.MILLISECONDS);
}
@Test(invocationCount = 10, threadPoolSize = 5)
public void testApiUnderLoad() {
given()
.spec(requestSpec)
.when()
.get("/posts")
.then()
.statusCode(200)
.time(lessThan(5000L), TimeUnit.MILLISECONDS)
.body("size()", greaterThan(0));
}
}
TestNG Configuration
<!-- testng.xml -->
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="API Testing Suite">
<test name="E-commerce API Tests">
<classes>
<class name="com.example.tests.EcommerceApiTests"/>
</classes>
</test>
<test name="User Authentication Tests">
<classes>
<class name="com.example.tests.UserAuthenticationTests"/>
</classes>
</test>
<test name="Performance Tests">
<classes>
<class name="com.example.tests.PerformanceTests"/>
</classes>
</test>
<test name="Schema Validation Tests">
<classes>
<class name="com.example.tests.JsonSchemaValidationTests"/>
</classes>
</test>
</suite>
Best Practices and Recommendations
1. Test Organization
- Use Page Object Model patterns for API endpoints
- Group related tests in test classes
- Implement proper test dependencies and priorities
2. Data Management
- Use TestNG DataProviders for parameterized tests
- Implement proper test data setup and cleanup
- Use external configuration files for environment-specific data
3. Assertions and Validations
- Use appropriate Hamcrest matchers for readable assertions
- Implement custom matchers for complex validations
- Validate both positive and negative scenarios
4. Error Handling
- Test error responses and status codes
- Validate error message formats and content
- Implement proper exception handling in test code
5. Reporting and Logging
- Enable request/response logging for debugging
- Use ExtentReports or Allure for comprehensive test reporting
- Implement custom listeners for enhanced reporting
Advanced REST-assured Features
Custom Filters and Interceptors
// Custom logging filter
public class CustomLoggingFilter implements Filter {
@Override
public Response filter(FilterableRequestSpecification requestSpec,
FilterableResponseSpecification responseSpec,
FilterContext ctx) {
System.out.println("Making request to: " + requestSpec.getURI());
Response response = ctx.next(requestSpec, responseSpec);
System.out.println("Received response with status: " + response.getStatusCode());
return response;
}
}
Multi-part File Upload Testing
@Test
public void testFileUpload() {
given()
.multiPart("file", new File("test-file.txt"))
.multiPart("description", "Test file upload")
.when()
.post("/upload")
.then()
.statusCode(200)
.body("filename", equalTo("test-file.txt"));
}
Conclusion
REST-assured provides a comprehensive and intuitive framework for API testing in Java environments. Its fluent interface, combined with powerful validation capabilities and extensive integration options, makes it an excellent choice for both simple and complex API testing scenarios.
The examples provided in this guide demonstrate real-world usage patterns that you can adapt and extend for your specific testing needs. By following the established patterns and best practices, you can create maintainable, robust, and efficient API test suites that integrate seamlessly with your CI/CD pipelines.
GitHub Repository Structure
For implementing these examples in your GitHub repository, consider the following project structure:
api-testing-framework/
├── src/
│ ├── test/
│ │ ├── java/
│ │ │ └── com/example/
│ │ │ ├── framework/
│ │ │ │ └── BaseApiTest.java
│ │ │ ├── tests/
│ │ │ │ ├── EcommerceApiTests.java
│ │ │ │ ├── UserAuthenticationTests.java
│ │ │ │ ├── JsonSchemaValidationTests.java
│ │ │ │ └── PerformanceTests.java
│ │ │ └── utils/
│ │ │ └── ApiTestUtils.java
│ │ └── resources/
│ │ ├── schemas/
│ │ │ ├── user-schema.json
│ │ │ └── products-list-schema.json
│ │ └── testdata/
│ │ └── test-config.properties
├── pom.xml
├── testng.xml
└── README.md
This structure provides a solid foundation for scaling your API testing framework as your application grows and evolves.
Top comments (1)
A standout point in the article is the seamless integration of REST Assured with test automation frameworks like JUnit, allowing developers to write readable and robust test cases. This integration promotes clean test organization, reusability, and CI/CD readiness, which is crucial in real-world agile development environments.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.