REST Assured (as discussed in API Testing Mastery: From REST to Contract Testing) has become the de facto standard for API testing in the Java ecosystem, providing a fluent, readable DSL (Domain Specific Language) for testing REST and HTTP-based services. This comprehensive guide explores REST Assured from fundamentals to advanced enterprise-level implementations.
Why REST Assured for API Testing?
REST Assured bridges the gap between API testing and Java development, offering native integration with existing Java test frameworks and build tools:
- Java-native solution: Seamless integration with JUnit, TestNG, Maven, and Gradle
- BDD-style syntax: Given-When-Then structure for readable test scenarios
- Rich assertion library: Hamcrest matchers and JsonPath/XmlPath support
- Authentication handling: Built-in support for OAuth, Basic Auth, and custom schemes
- Specification reuse: Define common request/response specifications once
- Performance: Direct HTTP client implementation without browser overhead
REST Assured vs Other Java Testing Frameworks
| Feature | REST Assured | Apache HttpClient | OkHttp | Spring RestTemplate |
|---|---|---|---|---|
| Readability | Excellent (BDD) | Low | Medium | Medium |
| Learning Curve | Low | High | Medium | Low |
| Response Validation | Built-in | Manual | Manual | Manual |
| JSON/XML Parsing | Native | External libs | External libs | Jackson |
| Authentication | Comprehensive | Manual | Manual | Built-in |
| Test Framework Integration | Excellent | Manual | Manual | Good |
Getting Started with REST Assured
Maven Dependency
<dependencies>
<!-- REST Assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
<!-- JSON Schema Validation -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit (as discussed in [Allure Framework: Creating Beautiful Test Reports](/blog/allure-framework-reporting)) 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Gradle Configuration
dependencies {
testImplementation 'io.rest-assured:rest-assured:5.5.0'
testImplementation 'io.rest-assured:json-schema-validator:5.5.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
test {
useJUnitPlatform()
}
Basic Request Patterns
Simple GET Request
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class BasicAPITests {
@Test
public void testGetUser() {
given()
.baseUri("https://api.example.com")
.header("Accept", "application/json")
.when()
.get("/users/1")
.then()
.statusCode(200)
.body("id", equalTo(1))
.body("email", notNullValue())
.body("name", containsString("John"));
}
}
POST Request with Body
@Test
public void testCreateUser() {
String requestBody = """
{
"name": "Jane Doe",
"email": "jane@example.com",
"role": "admin"
}
""";
given()
.contentType(ContentType.JSON)
.body(requestBody)
.when()
.post("/users")
.then()
.statusCode(201)
.body("id", notNullValue())
.body("name", equalTo("Jane Doe"))
.body("email", equalTo("jane@example.com"));
}
Using POJOs (Plain Old Java Objects)
// User model
public class User {
private String name;
private String email;
private String role;
// Constructors, getters, setters
public User(String name, String email, String role) {
this.name = name;
this.email = email;
this.role = role;
}
// Getters and setters omitted for brevity
}
@Test
public void testCreateUserWithPOJO() {
User newUser = new User("Alice Johnson", "alice@example.com", "user");
User createdUser =
given()
.contentType(ContentType.JSON)
.body(newUser)
.when()
.post("/users")
.then()
.statusCode(201)
.extract()
.as(User.class);
assertThat(createdUser.getName(), equalTo("Alice Johnson"));
assertThat(createdUser.getEmail(), equalTo("alice@example.com"));
}
Advanced Request Handling
Query Parameters and Path Parameters
@Test
public void testQueryParameters() {
given()
.queryParam("page", 2)
.queryParam("limit", 10)
.queryParam("sort", "name")
.when()
.get("/users")
.then()
.statusCode(200)
.body("data.size()", equalTo(10))
.body("pagination.page", equalTo(2));
}
@Test
public void testPathParameters() {
int userId = 123;
String resourceId = "abc-456";
given()
.pathParam("userId", userId)
.pathParam("resourceId", resourceId)
.when()
.get("/users/{userId}/resources/{resourceId}")
.then()
.statusCode(200)
.body("userId", equalTo(userId))
.body("resourceId", equalTo(resourceId));
}
Authentication Mechanisms
// Basic Authentication
@Test
public void testBasicAuth() {
given()
.auth().basic("username", "password")
.when()
.get("/protected")
.then()
.statusCode(200);
}
// Bearer Token
@Test
public void testBearerToken() {
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
given()
.auth().oauth2(token)
.when()
.get("/api/secure-endpoint")
.then()
.statusCode(200);
}
// Preemptive Authentication
@Test
public void testPreemptiveAuth() {
given()
.auth().preemptive().basic("admin", "admin123")
.when()
.get("/admin/dashboard")
.then()
.statusCode(200);
}
// Custom Header Authentication
@Test
public void testCustomHeaderAuth() {
given()
.header("X-API-Key", "your-api-key-here")
.header("X-Client-ID", "client-123")
.when()
.get("/api/data")
.then()
.statusCode(200);
}
Request and Response Specifications
Eliminate duplication with reusable specifications:
public class APITestBase {
protected static RequestSpecification requestSpec;
protected static ResponseSpecification responseSpec;
@BeforeAll
public static void setup() {
requestSpec = new RequestSpecBuilder()
.setBaseUri("https://api.example.com")
.setBasePath("/api/v1")
.addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json")
.setAuth(oauth2("access-token"))
.setContentType(ContentType.JSON)
.build();
responseSpec = new ResponseSpecBuilder()
.expectStatusCode(200)
.expectResponseTime(lessThan(2000L))
.expectHeader("Content-Type", containsString("json"))
.build();
RestAssured.requestSpecification = requestSpec;
RestAssured.responseSpecification = responseSpec;
}
}
// Using specifications
@Test
public void testWithSpecifications() {
given()
.spec(requestSpec)
.queryParam("filter", "active")
.when()
.get("/users")
.then()
.spec(responseSpec)
.body("data", notNullValue());
}
JSON Response Validation
JsonPath Queries
@Test
public void testComplexJSONValidation() {
given()
.get("/api/products")
.then()
.statusCode(200)
// Check array size
.body("products.size()", greaterThan(0))
// Check nested values
.body("products[0].name", notNullValue())
.body("products[0].price", greaterThan(0f))
// Find specific items
.body("products.findAll { it.category == 'electronics' }.size()", greaterThan(0))
// Check all items meet condition
.body("products.every { it.price > 0 }", equalTo(true))
// Sum values
.body("products.collect { it.price }.sum()", greaterThan(100f));
}
@Test
public void testNestedJSONPaths() {
given()
.get("/api/orders/123")
.then()
.body("order.id", equalTo(123))
.body("order.customer.name", equalTo("John Doe"))
.body("order.items.size()", equalTo(3))
.body("order.items[0].productId", notNullValue())
.body("order.shipping.address.city", equalTo("New York"))
.body("order.payment.status", equalTo("completed"));
}
Extracting Values for Reuse
@Test
public void testExtractAndReuse() {
// Extract single value
String userId =
given()
.body(newUser)
.when()
.post("/users")
.then()
.statusCode(201)
.extract()
.path("id");
// Use extracted value in subsequent request
given()
.pathParam("id", userId)
.when()
.get("/users/{id}")
.then()
.statusCode(200)
.body("id", equalTo(userId));
}
@Test
public void testExtractComplexResponse() {
Response response =
given()
.get("/api/products")
.then()
.statusCode(200)
.extract()
.response();
// Extract multiple values
List<String> productNames = response.jsonPath().getList("products.name");
List<Float> prices = response.jsonPath().getList("products.price", Float.class);
Map<String, Object> firstProduct = response.jsonPath().getMap("products[0]");
// Assertions on extracted data
assertThat(productNames, hasSize(greaterThan(0)));
assertThat(prices, everyItem(greaterThan(0f)));
assertThat(firstProduct, hasKey("id"));
}
JSON Schema Validation
import static io.restassured.module.jsv.JsonSchemaValidator.*;
@Test
public void testJSONSchemaValidation() {
given()
.get("/api/users/1")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/user-schema.json"));
}
// user-schema.json
/*
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["id", "email", "name"],
"properties": {
"id": {
"type": "integer"
},
"email": {
"type": "string",
"format": "email"
},
"name": {
"type": "string",
"minLength": 1
},
"role": {
"type": "string",
"enum": ["admin", "user", "guest"]
}
}
}
*/
Data-Driven Testing
Parameterized Tests with JUnit 5
@ParameterizedTest
@CsvSource({
"1, John Doe, john@example.com",
"2, Jane Smith, jane@example.com",
"3, Bob Johnson, bob@example.com"
})
public void testMultipleUsers(int id, String name, String email) {
given()
.pathParam("id", id)
.when()
.get("/users/{id}")
.then()
.statusCode(200)
.body("name", equalTo(name))
.body("email", equalTo(email));
}
@ParameterizedTest
@MethodSource("provideUserData")
public void testUserCreation(User user) {
given()
.contentType(ContentType.JSON)
.body(user)
.when()
.post("/users")
.then()
.statusCode(201)
.body("email", equalTo(user.getEmail()));
}
private static Stream<User> provideUserData() {
return Stream.of(
new User("Alice", "alice@test.com", "user"),
new User("Bob", "bob@test.com", "admin"),
new User("Charlie", "charlie@test.com", "moderator")
);
}
TestNG DataProvider
@DataProvider(name = "userData")
public Object[][] userData() {
return new Object[][] {
{"admin", "admin@example.com", 201},
{"user", "user@example.com", 201},
{"", "invalid", 400} // Negative test case
};
}
@Test(dataProvider = "userData")
public void testUserCreationWithDataProvider(String name, String email, int expectedStatus) {
User user = new User(name, email, "user");
given()
.contentType(ContentType.JSON)
.body(user)
.when()
.post("/users")
.then()
.statusCode(expectedStatus);
}
File Upload and Download
File Upload
@Test
public void testFileUpload() {
File imageFile = new File("src/test/resources/test-image.jpg");
given()
.multiPart("file", imageFile, "image/jpeg")
.multiPart("description", "Test image upload")
.when()
.post("/api/upload")
.then()
.statusCode(200)
.body("filename", equalTo("test-image.jpg"))
.body("size", greaterThan(0));
}
@Test
public void testMultipleFileUpload() {
File file1 = new File("src/test/resources/doc1.pdf");
File file2 = new File("src/test/resources/doc2.pdf");
given()
.multiPart("files", file1)
.multiPart("files", file2)
.when()
.post("/api/upload/multiple")
.then()
.statusCode(200)
.body("uploaded.size()", equalTo(2));
}
File Download
@Test
public void testFileDownload() {
byte[] fileContent =
given()
.pathParam("fileId", "abc123")
.when()
.get("/api/download/{fileId}")
.then()
.statusCode(200)
.header("Content-Type", "application/pdf")
.extract()
.asByteArray();
assertThat(fileContent.length, greaterThan(0));
// Save to file
File downloadedFile = new File("target/downloaded.pdf");
Files.write(downloadedFile.toPath(), fileContent);
}
Performance and Response Time Testing
@Test
public void testResponseTime() {
given()
.get("/api/products")
.then()
.statusCode(200)
.time(lessThan(500L), TimeUnit.MILLISECONDS);
}
@Test
public void testPerformanceMetrics() {
long start = System.currentTimeMillis();
Response response =
given()
.get("/api/heavy-operation")
.then()
.statusCode(200)
.extract()
.response();
long responseTime = response.getTime();
long totalTime = System.currentTimeMillis() - start;
assertThat(responseTime, lessThan(1000L));
assertThat(totalTime, lessThan(1500L));
System.out.println("Response time: " + responseTime + "ms");
System.out.println("Total time: " + totalTime + "ms");
}
CI/CD Integration
Maven Surefire Configuration
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
<systemPropertyVariables>
<api.base.uri>${env.API_BASE_URI}</api.base.uri>
<api.token>${env.API_TOKEN}</api.token>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
Jenkins Pipeline
pipeline {
agent any
environment {
API_BASE_URI = credentials('api-base-uri')
API_TOKEN = credentials('api-token')
}
stages {
stage('Checkout') {
steps {
git 'https://github.com/yourorg/api-tests.git'
}
}
stage('API Tests') {
steps {
sh 'mvn clean test -Dapi.base.uri=${API_BASE_URI} -Dapi.token=${API_TOKEN}'
}
}
stage('Publish Reports') {
steps {
junit '**/target/surefire-reports/*.xml'
publishHTML([
reportDir: 'target/site/serenity',
reportFiles: 'index.html',
reportName: 'API Test Report'
])
}
}
}
}
GitHub Actions
name: API Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
- name: Run API Tests
env:
API_BASE_URI: ${{ secrets.API_BASE_URI }}
API_TOKEN: ${{ secrets.API_TOKEN }}
run: mvn clean test
- name: Publish Test Results
uses: dorny/test-reporter@v1
if: always()
with:
name: API Test Results
path: target/surefire-reports/*.xml
reporter: java-junit
Best Practices
Organize Tests with Page Object Pattern
// UserEndpoint.java
public class UserEndpoint {
private static final String BASE_PATH = "/users";
public Response getAllUsers() {
return given()
.spec(APITestBase.requestSpec)
.when()
.get(BASE_PATH)
.then()
.extract()
.response();
}
public Response getUser(int userId) {
return given()
.spec(APITestBase.requestSpec)
.pathParam("id", userId)
.when()
.get(BASE_PATH + "/{id}")
.then()
.extract()
.response();
}
public Response createUser(User user) {
return given()
.spec(APITestBase.requestSpec)
.body(user)
.when()
.post(BASE_PATH)
.then()
.extract()
.response();
}
}
// Test class
public class UserAPITest {
private UserEndpoint userEndpoint = new UserEndpoint();
@Test
public void testGetAllUsers() {
Response response = userEndpoint.getAllUsers();
assertThat(response.statusCode(), equalTo(200));
}
}
Logging and Debugging
@Test
public void testWithLogging() {
given()
.log().all() // Log request
.get("/users")
.then()
.log().all() // Log response
.statusCode(200);
}
@Test
public void testConditionalLogging() {
given()
.log().ifValidationFails()
.get("/users")
.then()
.log().ifError()
.statusCode(200);
}
Conclusion
REST Assured provides a powerful, Java-native solution for API testing that integrates seamlessly into existing development workflows. Its BDD-style (as discussed in BDD: From Requirements to Automation) syntax, comprehensive validation capabilities, and rich ecosystem make it ideal for teams already invested in the Java stack.
Key advantages:
- Fluent, readable test syntax
- Deep integration with Java testing ecosystem
- Powerful JSON/XML validation
- Built-in authentication support
- Excellent CI/CD integration
- Active community and extensive documentation
Whether building microservices, testing REST APIs, or validating complex integration scenarios, REST Assured delivers the tools needed for comprehensive, maintainable API test automation.
Official Resources
See Also
- Jest & Testing Library: Modern Component Testing for React Applications - Master Jest and Testing Library for React component testing. Learn…
- API Testing Mastery: From REST to Contract Testing - Complete guide to API testing: REST vs GraphQL vs gRPC, mastering…
- K6: Modern Load Testing with JavaScript for DevOps Teams - Master K6 for modern performance testing: JavaScript-based load…
- Artillery Performance Testing: Modern Load Testing with YAML Scenarios - Complete Artillery performance testing guide covering YAML…
