Introduction

In our REST Assured series, we’ve explored topics like OAuth 2.0, API Key, and Bearer Token Authentication, Digest and Form Authentication, and GPath Expressions, building a robust foundation for API testing. Now, we’ll dive into Filters, a powerful feature in REST Assured that allows you to intercept and modify HTTP requests and responses. This guide demonstrates how to use Filters to streamline logging, authentication, and custom validations, with clear examples and best practices. It’s designed for beginners and experienced developers, ensuring you can enhance your API tests efficiently.

Key Point: Filters in REST Assured enable you to customize API testing by intercepting requests and responses, allowing for reusable logic like logging, authentication, or header manipulation.

What are Filters in REST Assured?

Filters are components in REST Assured that intercept HTTP requests and responses, allowing you to perform actions such as:

  • Modify Requests: Add headers, query parameters, or authentication tokens.
  • Inspect Responses: Validate status codes, headers, or response bodies.
  • Log Details: Capture request and response data for debugging or reporting.
  • Reuse Logic: Apply common functionality (e.g., authentication) across multiple tests.

Filters implement the io.restassured.filter.Filter interface and are applied using the filter() method in the request specification. REST Assured provides built-in Filters (e.g., LogFilter) and supports custom Filters for advanced use cases. We’ll use https://jsonplaceholder.typicode.com for examples and simulate a Bearer token scenario for authentication-related Filters.

Setting Up for Filter Testing

We’ll set up a Maven project with REST Assured, JUnit, and Allure for reporting, consistent with previous posts. No additional dependencies are needed for Filters.

Here’s the pom.xml, styled with your preferred Blogger format for XML syntax highlighting:



    4.0.0
    com.example
    rest-assured-tests
    1.0-SNAPSHOT

    
        11
        2.27.0
        1.9.22
    

    
        
            io.restassured
            rest-assured
            5.4.0
            test
        
        
            org.junit.jupiter
            junit-jupiter
            5.10.2
            test
        
        
            org.hamcrest
            hamcrest
            2.2
            test
        
        
            com.fasterxml.jackson.core
            jackson-databind
            2.15.2
            test
        
        
            io.qameta.allure
            allure-junit5
            ${allure.version}
            test
        
    

    
        
            
                org.apache.maven.plugins
                maven-surefire-plugin
                3.2.5
                
                    
                        -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                    
                
                
                    
                        org.aspectj
                        aspectjweaver
                        ${aspectj.version}
                    
                
            
            
                io.qameta.allure
                allure-maven
                2.12.0
                
                    ${allure.version}
                
            
        
    

No additional setup is required for https://jsonplaceholder.typicode.com. For authentication-related Filters, we’ll use a mock Bearer token scenario.

Using Filters in REST Assured

Let’s explore how to use built-in and custom Filters to enhance API testing.

Example 1: Using Built-in LogFilter

Use the built-in LogFilter to log request and response details.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.filter.log.LogFilter;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Filters Testing")
public class LogFilterTest {

    @Test
    @Description("Test built-in LogFilter for request and response logging")
    public void testLogFilter() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .filter(new LogFilter())
        .when()
            .get("/posts/1")
        .then()
            .statusCode(200)
            .body("id", equalTo(1))
            .body("title", notNullValue());
    }
}

Explanation:

  • filter(new LogFilter()): Applies the LogFilter to log request and response details.
  • Logs are printed to the console, useful for debugging.
  • Similar to log().all() but can be reused across tests.
Important: LogFilter provides detailed logging but can generate verbose output. Use selectively in production tests.

Example 2: Custom Filter for Adding Headers

Create a custom Filter to add a common header (e.g., API key) to all requests.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Filters Testing")
public class CustomHeaderFilterTest {

    public static class ApiKeyFilter implements Filter {
        @Override
        public Response filter(FilterableRequestSpecification requestSpec,
                               FilterableResponseSpecification responseSpec,
                               FilterContext ctx) {
            requestSpec.header("X-Api-Key", "my-api-key");
            return ctx.next(requestSpec, responseSpec);
        }
    }

    @Test
    @Description("Test custom Filter for adding API key header")
    public void testApiKeyFilter() {
        RestAssured.baseURI = "https://httpbin.org";

        given()
            .filter(new ApiKeyFilter())
        .when()
            .get("/headers")
        .then()
            .statusCode(200)
            .body("headers.X-Api-Key", equalTo("my-api-key"));
    }
}

Explanation:

  • ApiKeyFilter: Implements the Filter interface to add an X-Api-Key header.
  • requestSpec.header(...): Modifies the request to include the header.
  • ctx.next(...): Proceeds with the request after modification.

Example 3: Custom Filter for Bearer Token Authentication

Create a custom Filter to inject a Bearer token into requests.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Filters Testing")
public class BearerTokenFilterTest {

    public static class BearerTokenFilter implements Filter {
        private final String token;

        public BearerTokenFilter(String token) {
            this.token = token;
        }

        @Override
        public Response filter(FilterableRequestSpecification requestSpec,
                               FilterableResponseSpecification responseSpec,
                               FilterContext ctx) {
            requestSpec.header("Authorization", "Bearer " + token);
            return ctx.next(requestSpec, responseSpec);
        }
    }

    @Test
    @Description("Test custom Filter for Bearer token authentication")
    public void testBearerTokenFilter() {
        RestAssured.baseURI = "https://httpbin.org";

        given()
            .filter(new BearerTokenFilter("my-bearer-token"))
        .when()
            .get("/headers")
        .then()
            .statusCode(200)
            .body("headers.Authorization", equalTo("Bearer my-bearer-token"));
    }
}

Explanation:

  • BearerTokenFilter: Adds a Bearer token to the Authorization header.
  • The token is passed via the Filter’s constructor, making it reusable.
  • Ideal for scenarios where tokens are obtained externally (e.g., OAuth 2.0).
Pro Tip: Use Filters for authentication to centralize token management and avoid repetitive code in tests.

Example 4: Custom Filter for Response Validation

Create a custom Filter to validate response headers (e.g., Content-Type).


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.junit.jupiter.api.Assertions.*;

@Feature("Filters Testing")
public class ResponseValidationFilterTest {

    public static class ContentTypeFilter implements Filter {
        @Override
        public Response filter(FilterableRequestSpecification requestSpec,
                               FilterableResponseSpecification responseSpec,
                               FilterContext ctx) {
            Response response = ctx.next(requestSpec, responseSpec);
            assertEquals("application/json; charset=utf-8", response.header("Content-Type"),
                "Unexpected Content-Type");
            return response;
        }
    }

    @Test
    @Description("Test custom Filter for response header validation")
    public void testContentTypeFilter() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .filter(new ContentTypeFilter())
        .when()
            .get("/posts/1")
        .then()
            .statusCode(200)
            .body("id", equalTo(1));
    }
}

Explanation:

  • ContentTypeFilter: Validates the Content-Type header in the response.
  • assertEquals(...): Throws an assertion error if the header is incorrect.
  • Applied before test-specific validations, ensuring consistent checks.

Example 5: Combining Multiple Filters

Combine multiple Filters for logging, authentication, and response validation.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.filter.log.LogFilter;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Filters Testing")
public class MultipleFiltersTest {

    @Test
    @Description("Test combining multiple Filters")
    public void testMultipleFilters() {
        RestAssured.baseURI = "https://httpbin.org";

        given()
            .filter(new LogFilter())
            .filter(new BearerTokenFilterTest.BearerTokenFilter("my-bearer-token"))
            .filter(new ResponseValidationFilterTest.ContentTypeFilter())
        .when()
            .get("/headers")
        .then()
            .statusCode(200)
            .body("headers.Authorization", equalTo("Bearer my-bearer-token"));
    }
}

Explanation:

  • Combines LogFilter, BearerTokenFilter, and ContentTypeFilter.
  • Filters are applied in the order specified, enabling layered functionality.
  • Reduces test-specific code by centralizing common logic.

Integrating with Allure Reporting

Document Filter-based tests with Allure, attaching request and response details.


import io.qameta.allure.Allure;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Filters Testing")
public class AllureFilterTest {

    public static class AllureLoggingFilter implements Filter {
        @Override
        public Response filter(FilterableRequestSpecification requestSpec,
                               FilterableResponseSpecification responseSpec,
                               FilterContext ctx) {
            Response response = ctx.next(requestSpec, responseSpec);
            Allure.addAttachment("Request URI", "text/plain", requestSpec.getURI(), ".txt");
            Allure.addAttachment("Response Body", "application/json", response.asString(), ".json");
            return response;
        }
    }

    @Test
    @Description("Test custom Filter with Allure reporting")
    public void testFilterWithAllure() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .filter(new AllureLoggingFilter())
        .when()
            .get("/posts/1")
        .then()
            .statusCode(200)
            .body("id", equalTo(1))
            .body("title", notNullValue());
    }
}

Explanation:

  • AllureLoggingFilter: Attaches the request URI and response body to the Allure report.
  • Allure.addAttachment: Enhances report visibility.
  • Run mvn clean test and mvn allure:serve to view the report.

Integrating with CI/CD

Add Filter-based 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 Filter-based tests with mvn clean test.
  • Publishes Allure reports to GitHub Pages for visibility.

Tips for Beginners

  • Start with Built-in Filters: Use LogFilter to understand Filter behavior before creating custom ones.
  • Keep Filters Focused: Each Filter should handle a single responsibility (e.g., logging, authentication).
  • Reuse Filters: Apply Filters globally using RestAssured.filters(...) for common logic.
  • Debug with Logging: Use Filters to log specific headers or parameters for troubleshooting.
Troubleshooting Tip: If a Filter fails, add logging within the Filter to inspect request/response details and verify the Filter’s logic.

What’s Next?

This post enhances our REST Assured series by introducing Filters, a versatile tool for customizing API tests. 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!