Introduction

In our REST Assured series, we’ve covered topics like PATCH Requests, Security Testing, and Contract Testing, learning how to test various aspects of APIs. Now, we’ll explore Response Specification, a powerful feature in REST Assured for defining reusable response validation rules. This guide demonstrates how to create and use Response Specifications to ensure consistent and efficient API testing. It’s designed for beginners and experienced developers, providing clear explanations and practical examples.

Key Point: Response Specification in REST Assured allows you to define reusable validation rules for API responses, reducing code duplication and ensuring consistent testing across endpoints.

What is Response Specification?

A ResponseSpecification in REST Assured is an object that encapsulates common response validation rules, such as expected status codes, headers, content types, or body content. By defining these rules once, you can apply them to multiple tests, ensuring consistent validation across your test suite.

Benefits of using Response Specification:

  • Consistency: Ensures all tests validate responses uniformly.
  • Reusability: Reduces repetitive validation code.
  • Maintainability: Centralizes updates to validation rules.

REST Assured provides the ResponseSpecBuilder to create Response Specifications, which can be applied using the spec() method in the then() block. We’ll use the public API https://jsonplaceholder.typicode.com for examples, which provides reliable responses.

Setting Up for Response Specification

To use Response Specification, we’ll set up a Maven project with REST Assured, JUnit, and Allure for reporting. The setup is similar to previous posts in the series.

Here’s the pom.xml with required dependencies:


<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <allure.version>2.27.0</allure.version>
    <aspectj.version>1.9.22</aspectj.version>
</properties>
<dependencies>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest</artifactId>
        <version>2.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.qameta.allure</groupId>
        <artifactId>allure-junit5</artifactId>
        <version>${allure.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.5</version>
            <configuration>
                <argLine>
                    -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                </argLine>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjweaver</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
            </dependencies>
        </plugin>
        <plugin>
            <groupId>io.qameta.allure</groupId>
            <artifactId>allure-maven</artifactId>
            <version>2.12.0</version>
            <configuration>
                <reportVersion>${allure.version}</reportVersion>
            </configuration>
        </plugin>
    </plugins>
</build>

No additional setup is required for https://jsonplaceholder.typicode.com, as it provides consistent JSON responses for testing.

Creating and Applying Response Specification

Let’s explore how to create Response Specifications and apply them to various API tests.

Example 1: Basic Response Specification

Define a Response Specification for common response attributes and apply it to a GET request.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.specification.ResponseSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Response Specification Testing")
public class BasicResponseSpecTest {

    private ResponseSpecification responseSpec;

    @BeforeEach
    public void setup() {
        responseSpec = new ResponseSpecBuilder()
            .expectStatusCode(200)
            .expectContentType("application/json")
            .expectHeader("Server", containsString("cloudflare"))
            .build();
        }
    }

    @Test
    @Description("Verify GET request with Response Specification")
    public void testGetPostWithSpec() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .log().all()
            .when()
                .get("/posts/1")
            .then()
                .spec(responseSpec)
                .body("id", equalTo(1))
                .body("title", notNullValue());
    }
    }
}

Explanation:

  • ResponseSpecBuilder: Creates a specification with common validations (status 200, JSON content type, server header).
  • spec(responseSpec): Applies the specification in the then() block.
  • Additional validations (e.g., id, title) are specific to the test.
Important: Response Specifications reduce repetitive validation code, making tests cleaner and easier to maintain.

Example 2: Response Specification with Body Validation

Include body validation in the Response Specification using Hamcrest matchers or JSON schema.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.specification.ResponseSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static io.restassured.module.jsv.JsonSchemaValidator.*;
import static org.hamcrest.Matchers.*;

@Feature("Response Specification Testing")
public class BodyValidationSpecTest {

    private ResponseSpecification responseSpec;

    @BeforeEach
    public void setup() {
        responseSpec = new ResponseSpecBuilder()
            .expectStatusCode(200)
            .expectContentType("application/json")
            .expectBody("userId", notNullValue())
            .expectBody(matchesJsonSchemaInClasspath("post-schema.json"))
            .build();
    }

    @Test
    @Description("Verify GET request with body validation in Response Specification")
    public void testGetPostWithBodySpec() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .log().all()
            .when()
                .get("/posts/1")
            .then()
                .spec(responseSpec)
                .body("id", equalTo(1));
    }
}

Create a JSON schema file (src/test/resources/post-schema.json):


{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "id": { "type": "integer" },
    "userId": { "type": "integer" },
    "title": { "type": "string" },
    "body": { "type": "string" }
  },
  "required": ["id", "userId", "title", "body"]
}

Explanation:

  • expectBody: Validates specific fields (e.g., userId) using Hamcrest matchers.
  • matchesJsonSchemaInClasspath: Ensures the response matches the JSON schema.
  • The test adds specific validations (e.g., id) beyond the specification.
Pro Tip: Use JSON schema validation in Response Specifications for complex APIs to ensure response structure compliance.

Example 3: Handling Error Responses

Create a Response Specification for error scenarios, such as 404 Not Found.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.specification.ResponseSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Response Specification Testing")
public class ErrorResponseSpecTest {

    private ResponseSpecification notFoundSpec;

    @BeforeEach
    public void setup() {
        notFoundSpec = new ResponseSpecBuilder()
            .expectStatusCode(404)
            .expectContentType("application/json")
            .expectBody(isEmptyOrNullString())
            .build();
    }

    @Test
    @Description("Verify error response for non-existent resource with Response Specification")
    public void testNotFoundWithSpec() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .log().ifValidationFails()
            .when()
                .get("/posts/9999")
            .then()
                .spec(notFoundSpec);
    }
}

Explanation:

  • notFoundSpec: Defines expectations for a 404 response (status, content type, empty body).
  • log().ifValidationFails(): Logs details only if the test fails, keeping console output clean.

Combining Request and Response Specifications

Use RequestSpecification and ResponseSpecification together for a complete test setup.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Response Specification Testing")
public class CombinedSpecTest {

    private RequestSpecification requestSpec;
    private ResponseSpecification responseSpec;

    @BeforeEach
    public void setup() {
        requestSpec = new RequestSpecBuilder()
            .setBaseUri("https://jsonplaceholder.typicode.com")
            .addHeader("Accept", "application/json")
            .log(LogDetail.ALL)
            .build();

        responseSpec = new ResponseSpecBuilder()
            .expectStatusCode(200)
            .expectContentType("application/json")
            .expectBody("id", notNullValue())
            .build();
    }

    @Test
    @Description("Verify GET request with combined Request and Response Specifications")
    public void testCombinedSpec() {
        given()
            .spec(requestSpec)
            .pathParam("postId", 1)
            .when()
                .get("/posts/{postId}")
            .then()
                .spec(responseSpec)
                .body("title", notNullValue());
    }
}

Explanation:

  • requestSpec: Configures the base URI, headers, and logging.
  • responseSpec: Validates common response attributes.
  • Combines both for a clean, reusable test setup.
Tip: Store specifications in a utility class or base test class to reuse across multiple test suites.

Integrating with Allure Reporting

Document Response Specification tests with Allure, attaching request/response details.


import io.qameta.allure.Allure;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.response.Response;
import io.restassured.specification.ResponseSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Response Specification Testing")
public class AllureResponseSpecTest {

    private ResponseSpecification responseSpec;

    @BeforeEach
    public void setup() {
        responseSpec = new ResponseSpecBuilder()
            .expectStatusCode(200)
            .expectContentType("application/json")
            .build();
    }

    @Test
    @Description("Verify GET request with Response Specification and Allure reporting")
    public void testWithAllure() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        Response response = given()
            .log().all()
            .when()
                .get("/posts/1")
            .then()
                .spec(responseSpec)
                .body("id", equalTo(1))
                .extract().response();

        Allure.addAttachment("Response Body", "application/json", response.asString(), ".json");
    }
}

Explanation:

  • Allure.addAttachment: Attaches the response body to the Allure report.
  • Run mvn clean test and mvn allure:serve to view the report.

Integrating with CI/CD

Add Response Specification tests to a GitHub Actions pipeline for automation.

Create or update .github/workflows/ci.yml:


name: REST Assured CI Pipeline

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up JDK 11
        uses: actions/setup-java@v4
        with:
          java-version: '11'
          distribution: 'temurin'

      - name: Cache Maven dependencies
        uses: actions/cache@v4
        with:
          path: ~/.m2/repository
          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
          restore-keys: ${{ runner.os }}-maven-

      - name: Run tests
        run: mvn clean test

      - name: Generate Allure report
        run: mvn allure:report

      - name: Upload Allure results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: allure-results
          path: target/allure-results

      - name: Publish Allure report
        if: always()
        uses: simple-elf/allure-report-action@v1.7
        with:
          allure_results: target/allure-results
          gh_pages: gh-pages
          allure_report: allure-report

      - name: Deploy report to GitHub Pages
        if: always()
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: allure-report

Explanation:

  • Runs tests with Response Specifications using mvn clean test.
  • Publishes Allure reports to GitHub Pages for visibility.

Tips for Beginners

  • Start Simple: Begin with basic Response Specifications for status codes and content types.
  • Reuse Across Tests: Define specifications in a central location for multiple endpoints.
  • Combine with Logging: Use log().ifValidationFails() to debug specification failures.
  • Validate Schemas: Include JSON schema validation for complex APIs to ensure response integrity.
Troubleshooting Tip: If a test fails with a Response Specification, check the specification’s expectations against the API’s actual response. Enable log().all() to inspect details.

What’s Next?

This post enhances our REST Assured series by introducing Response Specification, a key feature for efficient response validation. To continue your learning, consider exploring:

  • GraphQL Testing: Testing GraphQL APIs with REST Assured.
  • Advanced Allure Reporting: Customizing reports with environments and categories.
  • End-to-End Testing: Combining REST Assured with Selenium for UI and API testing.
Stay tuned for more testing tutorials!