Introduction

In our REST Assured series, we’ve covered topics like Asynchronous Requests, Handling Timeouts, and Custom Assertions, equipping you with tools to create robust API tests. Now, we’ll explore API Chaining, a technique for testing scenarios where the response of one API call is used as input for subsequent calls. This guide demonstrates how to implement API chaining with REST Assured, with practical examples and best practices. It’s designed for beginners and experienced developers, ensuring you can test complex API workflows effectively.

Key Point: API Chaining in REST Assured enables testing of multi-step API interactions by passing data between requests, ensuring seamless integration testing.

What is API Chaining?

API Chaining involves making a sequence of API calls where the output (e.g., response data, IDs, or tokens) of one request is used as input for the next. This is common in scenarios like:

  • Creating and Retrieving Resources: Create a resource (e.g., a post) and fetch its details or related data (e.g., comments).
  • Authentication Flows: Obtain an access token and use it for authenticated requests.
  • Workflow Testing: Validate end-to-end business processes involving multiple APIs.

REST Assured supports API chaining by extracting data from responses (e.g., via JSONPath, POJOs, or headers) and passing it to subsequent requests. We’ll use https://jsonplaceholder.typicode.com for examples, chaining calls to endpoints like /posts, /comments, and /users.

Setting Up for API Chaining

We’ll set up a Maven project with REST Assured, JUnit 5, and Allure for reporting, consistent with previous posts. No additional dependencies are needed for API chaining, as REST Assured’s response extraction capabilities are sufficient.

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



    4.0.0
    com.example
    rest-assured-api-chaining
    1.0-SNAPSHOT

    
        11
        2.27.0
        1.9.22
    

    
        
            io.rest-assured
            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. We’ll extract response data using JSONPath and POJOs for chaining.

Implementing API Chaining

Let’s explore how to implement API chaining with REST Assured, covering basic chaining, POJO-based chaining, multi-step workflows, and Allure integration.

Example 1: Basic API Chaining with JSONPath

Create a post and fetch its comments using the post ID from the response.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("API Chaining with REST Assured")
public class BasicChainingTest {

    @BeforeEach
    public void setup() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
    }

    @Test
    @DisplayName("Test basic API chaining")
    @Description("Test creating a post and fetching its comments")
    public void testBasicChaining() {
        // Step 1: Create a post
        String requestBody = "{\"title\": \"Test Post\", \"body\": \"This is a test\", \"userId\": 1}";
        Response createResponse = given()
            .header("Content-Type", "application/json")
            .body(requestBody)
            .log().all()
        .when()
            .post("/posts")
        .then()
            .log().all()
            .statusCode(201)
            .body("title", equalTo("Test Post"))
            .extract().response();

        // Extract post ID
        int postId = createResponse.path("id");

        // Step 2: Fetch comments for the post
        given()
            .log().all()
        .when()
            .get("/posts/{postId}/comments", postId)
        .then()
            .log().all()
            .statusCode(200)
            .body("[0].postId", equalTo(postId));
    }
}

Explanation:

  • Creates a post with a POST request to /posts.
  • Extracts the id from the response using path("id").
  • Uses the postId to fetch comments with a GET request to /posts/{postId}/comments.
  • Validates that the comments are associated with the created post.
Important: Use JSONPath to extract specific fields from responses for simple chaining scenarios.

Example 2: API Chaining with POJOs

Use POJOs to chain a post creation with fetching its details.

Reuse the Post POJO from previous posts:


public class Post {
    private int id;
    private int userId;
    private String title;
    private String body;

    public Post() {}

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public int getUserId() { return userId; }
    public void setUserId(int userId) { this.userId = userId; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public String getBody() { return body; }
    public void setBody(String body) { this.body = body; }
}

Test code:


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.junit.jupiter.api.Assertions.*;

@Feature("API Chaining with REST Assured")
public class POJOChainingTest {

    @BeforeEach
    public void setup() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
    }

    @Test
    @DisplayName("Test API chaining with POJO")
    @Description("Test creating a post and fetching its details using POJO")
    public void testPOJOChaining() {
        // Step 1: Create a post using POJO
        Post newPost = new Post();
        newPost.setTitle("Test Post");
        newPost.setBody("This is a test");
        newPost.setUserId(1);

        Post createdPost = given()
            .header("Content-Type", "application/json")
            .body(newPost)
            .log().all()
        .when()
            .post("/posts")
        .then()
            .log().all()
            .statusCode(201)
            .extract().as(Post.class);

        // Step 2: Fetch the post details
        Post fetchedPost = given()
            .log().all()
        .when()
            .get("/posts/{id}", createdPost.getId())
        .then()
            .log().all()
            .statusCode(200)
            .extract().as(Post.class);

        // Validate
        assertEquals(createdPost.getId(), fetchedPost.getId(), "Post IDs should match");
        assertEquals("Test Post", fetchedPost.getTitle(), "Title should match");
    }
}

Explanation:

  • Serializes a Post POJO for the POST request.
  • Deserializes the response to a Post POJO to extract the ID.
  • Fetches the post details using the ID and validates the response POJO.

Example 3: Multi-Step API Chaining

Chain multiple API calls: create a user, create a post for the user, and fetch comments for the post.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("API Chaining with REST Assured")
public class MultiStepChainingTest {

    @BeforeEach
    public void setup() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
    }

    @Test
    @DisplayName("Test multi-step API chaining")
    @Description("Test creating a user, a post, and fetching comments")
    public void testMultiStepChaining() {
        // Step 1: Create a user
        String userBody = "{\"name\": \"Test User\", \"username\": \"testuser\", \"email\": \"test@example.com\"}";
        Response userResponse = given()
            .header("Content-Type", "application/json")
            .body(userBody)
            .log().all()
        .when()
            .post("/users")
        .then()
            .log().all()
            .statusCode(201)
            .extract().response();

        int userId = userResponse.path("id");

        // Step 2: Create a post for the user
        String postBody = String.format("{\"title\": \"User Post\", \"body\": \"Post content\", \"userId\": %d}", userId);
        Response postResponse = given()
            .header("Content-Type", "application/json")
            .body(postBody)
            .log().all()
        .when()
            .post("/posts")
        .then()
            .log().all()
            .statusCode(201)
            .body("userId", equalTo(userId))
            .extract().response();

        int postId = postResponse.path("id");

        // Step 3: Fetch comments for the post
        given()
            .log().all()
        .when()
            .get("/posts/{postId}/comments", postId)
        .then()
            .log().all()
            .statusCode(200)
            .body("[0].postId", equalTo(postId));
    }
}

Explanation:

  • Creates a user and extracts the userId.
  • Creates a post using the userId and extracts the postId.
  • Fetches comments for the postId and validates the association.
  • Simulates a real-world workflow with multiple dependencies.
Pro Tip: Break complex chains into smaller, reusable methods to improve test maintainability.

Example 4: API Chaining with Authentication

Simulate chaining an authentication request with an authenticated API call.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("API Chaining with REST Assured")
public class AuthChainingTest {

    @BeforeEach
    public void setup() {
        RestAssured.baseURI = "https://httpbin.org";
    }

    @Test
    @DisplayName("Test API chaining with authentication")
    @Description("Test obtaining a token and using it for an authenticated request")
    public void testAuthChaining() {
        // Step 1: Simulate obtaining a token
        String authBody = "{\"username\": \"test\", \"password\": \"pass\"}";
        Response authResponse = given()
            .header("Content-Type", "application/json")
            .body(authBody)
            .log().all()
        .when()
            .post("/post") // httpbin echoes the request body
        .then()
            .log().all()
            .statusCode(200)
            .extract().response();

        // Extract simulated token (using echoed JSON as a placeholder)
        String token = authResponse.path("json.username"); // Simulate token as "test"

        // Step 2: Use token for authenticated request
        given()
            .header("Authorization", "Bearer " + token)
            .log().all()
        .when()
            .get("/bearer")
        .then()
            .log().all()
            .statusCode(200)
            .body("authenticated", equalTo(true));
    }
}

Explanation:

  • Simulates obtaining a token via a POST to /post (httpbin echoes the request).
  • Extracts a placeholder token (username “test”) from the response.
  • Uses the token in an Authorization header for a GET to /bearer.
  • Validates authenticated access (httpbin confirms the Bearer token).

Example 5: API Chaining with Allure

Integrate API chaining with Allure reporting.


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

@Feature("API Chaining with REST Assured")
public class AllureChainingTest {

    @BeforeEach
    public void setup() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
    }

    @Test
    @DisplayName("Test API chaining with Allure")
    @Description("Test creating a post and fetching comments with Allure reporting")
    public void testChainingWithAllure() {
        // Step 1: Create a post
        String requestBody = "{\"title\": \"Test Post\", \"body\": \"This is a test\", \"userId\": 1}";
        Response createResponse = given()
            .header("Content-Type", "application/json")
            .body(requestBody)
            .log().all()
        .when()
            .post("/posts")
        .then()
            .log().all()
            .statusCode(201)
            .body("title", equalTo("Test Post"))
            .extract().response();

        Allure.addAttachment("Create Post Response", "application/json", createResponse.asString(), ".json");
        int postId = createResponse.path("id");

        // Step 2: Fetch comments
        Response commentsResponse = given()
            .log().all()
        .when()
            .get("/posts/{postId}/comments", postId)
        .then()
            .log().all()
            .statusCode(200)
            .body("[0].postId", equalTo(postId))
            .extract().response();

        Allure.addAttachment("Comments Response", "application/json", commentsResponse.asString(), ".json");
    }
}

Explanation:

  • Attaches each API response to Allure for debugging.
  • Chains a post creation with comment retrieval.
  • Run mvn clean test and mvn allure:serve to view the report.

Integrating with CI/CD

Add API chaining tests to a GitHub Actions pipeline for automation.

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


name: REST Assured API Chaining 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 API chaining tests with mvn clean test.
  • Publishes Allure reports to GitHub Pages for visibility.

Tips for Beginners

  • Extract Only Necessary Data: Use JSONPath or POJOs to extract relevant fields for chaining.
  • Validate Intermediate Steps: Assert responses at each step to catch errors early.
  • Enable Logging: Use log().all() to debug chaining failures.
  • Organize Tests: Break complex chains into smaller methods or classes for clarity.
Troubleshooting Tip: If an API chain fails, check logs for each step’s request and response, and verify extracted data matches the expected format.

What’s Next?

This post enhances our REST Assured series by introducing API Chaining, a powerful technique for testing multi-step API workflows. 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!