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 usingpath("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 thepostId
. - 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
andmvn 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.