DEV Community

Cover image for REST-assured API Testing Framework: Complete Guide with Real-World Examples

REST-assured API Testing Framework: Complete Guide with Real-World Examples

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);
Enter fullscreen mode Exit fullscreen mode

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

  1. RequestSpecification: Defines request parameters, headers, and body
  2. Response: Captures and validates API responses
  3. ValidatableResponse: Provides fluent validation methods
  4. JsonPath/XmlPath: Extract and validate data from responses
  5. 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>
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
    }
}
Enter fullscreen mode Exit fullscreen mode

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"));
    }
}
Enter fullscreen mode Exit fullscreen mode

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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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"));
    }
}
Enter fullscreen mode Exit fullscreen mode

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"]
}
Enter fullscreen mode Exit fullscreen mode

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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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"));
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This structure provides a solid foundation for scaling your API testing framework as your application grows and evolves.

Repositorio github

https://github.com/LuzkalidGM/api-testing-framework.git

Top comments (1)

Collapse
 
draigo15 profile image
rodrigo_lira

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.