Introduction
In our REST Assured series, we’ve explored topics like API Chaining, Asynchronous Requests, and Handling Timeouts, providing a comprehensive toolkit for API testing. Now, we’ll dive into Parallel Execution, a technique to run REST Assured tests concurrently to reduce test suite execution time. This guide demonstrates how to implement parallel execution with REST Assured and JUnit 5, with practical examples and best practices. It’s designed for beginners and experienced developers, ensuring your API tests run efficiently.
Key Point: Parallel Execution in REST Assured leverages multi-threading to run tests concurrently, significantly improving performance for large test suites.
What is Parallel Execution?
Parallel Execution involves running multiple tests simultaneously across multiple threads or processes to reduce the overall execution time of a test suite. In API testing with REST Assured, parallel execution is useful for:
- Speed: Executing independent tests concurrently to save time.
- Scalability: Handling large test suites with hundreds or thousands of tests.
- Resource Utilization: Maximizing CPU and network resources on test machines.
- CI/CD Integration: Accelerating feedback loops in automated pipelines.
JUnit 5, combined with Maven’s Surefire or Failsafe plugins, provides robust support for parallel test execution. REST Assured tests, being stateless by nature, are well-suited for parallel execution, provided thread-safety is ensured. We’ll use https://jsonplaceholder.typicode.com
for examples, running tests against endpoints like /posts
and /comments
in parallel.
Setting Up for Parallel Execution
We’ll set up a Maven project with REST Assured, JUnit 5, and Allure for reporting, consistent with previous posts. We’ll configure the Maven Surefire Plugin to enable parallel execution with JUnit 5.
Here’s the pom.xml
, styled with your preferred Blogger format for XML syntax highlighting:
4.0.0
com.example
rest-assured-parallel
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"
classes
4
false
org.aspectj
aspectjweaver
${aspectj.version}
io.qameta.allure
allure-maven
2.12.0
${allure.version}
Key Configuration:
parallel>classes
: Runs test classes in parallel.threadCount>4
: Limits execution to 4 threads to avoid overwhelming the system.useUnlimitedThreads>false
: Enforces the thread count limit.
https://jsonplaceholder.typicode.com
. We’ll create multiple test classes to demonstrate parallel execution.
Implementing Parallel Execution
Let’s explore how to implement parallel execution with REST Assured, covering basic parallel tests, thread-safe configurations, handling shared resources, and Allure integration.
Example 1: Basic Parallel Test Execution
Run multiple test classes in parallel to test different endpoints.
Test Class 1: PostsTest
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.hamcrest.Matchers.*;
@Feature("Parallel Execution with REST Assured")
public class PostsTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test fetching a single post")
@Description("Test retrieving a post by ID")
public void testGetPost() {
given()
.log().all()
.when()
.get("/posts/1")
.then()
.log().all()
.statusCode(200)
.body("id", equalTo(1))
.body("title", notNullValue());
}
@Test
@DisplayName("Test fetching all posts")
@Description("Test retrieving all posts")
public void testGetAllPosts() {
given()
.log().all()
.when()
.get("/posts")
.then()
.log().all()
.statusCode(200)
.body("size()", equalTo(100));
}
}
Test Class 2: CommentsTest
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.hamcrest.Matchers.*;
@Feature("Parallel Execution with REST Assured")
public class CommentsTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test fetching comments for a post")
@Description("Test retrieving comments by post ID")
public void testGetComments() {
given()
.log().all()
.when()
.get("/posts/1/comments")
.then()
.log().all()
.statusCode(200)
.body("[0].postId", equalTo(1));
}
@Test
@DisplayName("Test fetching a single comment")
@Description("Test retrieving a comment by ID")
public void testGetSingleComment() {
given()
.log().all()
.when()
.get("/comments/1")
.then()
.log().all()
.statusCode(200)
.body("id", equalTo(1));
}
}
Explanation:
- Two test classes (
PostsTest
andCommentsTest
) run in parallel, as configured inpom.xml
. - Each class tests independent endpoints (
/posts
,/comments
). - Run
mvn clean test
to execute tests; Maven Surefire will distribute tests across 4 threads.
Important: Ensure tests are independent to avoid race conditions during parallel execution.
Example 2: Parallel Execution with POJOs
Run tests in parallel using POJOs for response validation.
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 Class: ParallelPOJOTest
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("Parallel Execution with REST Assured")
public class ParallelPOJOTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test fetching post as POJO in parallel")
@Description("Test retrieving a post and mapping to POJO")
public void testGetPostAsPOJO() {
Post post = given()
.log().all()
.when()
.get("/posts/1")
.then()
.log().all()
.statusCode(200)
.extract().as(Post.class);
assertEquals(1, post.getId(), "Post ID should be 1");
assertNotNull(post.getTitle(), "Title should not be null");
}
@Test
@DisplayName("Test creating post with POJO in parallel")
@Description("Test creating a post using POJO")
public void testCreatePostWithPOJO() {
Post newPost = new Post();
newPost.setTitle("Test Post");
newPost.setBody("This is a test");
newPost.setUserId(1);
given()
.header("Content-Type", "application/json")
.body(newPost)
.log().all()
.when()
.post("/posts")
.then()
.log().all()
.statusCode(201)
.body("title", equalTo("Test Post"));
}
}
Explanation:
- Tests POJO-based operations (GET and POST) in parallel with other test classes.
- Uses thread-safe POJO deserialization and serialization.
- Ensures independence by avoiding shared state.
Example 3: Thread-Safe Configuration
Ensure thread-safety when using shared REST Assured configurations.
Test Class: ThreadSafeTest
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.config.RestAssuredConfig;
import io.restassured.config.HttpClientConfig;
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("Parallel Execution with REST Assured")
public class ThreadSafeTest {
@BeforeEach
public void setup() {
// Avoid modifying global RestAssured state
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test thread-safe GET request")
@Description("Test GET request with per-request configuration")
public void testThreadSafeGet() {
given()
.config(RestAssuredConfig.config().httpClient(HttpClientConfig.httpClientConfig()
.setParam("http.socket.timeout", 5000)))
.log().all()
.when()
.get("/posts/2")
.then()
.log().all()
.statusCode(200)
.body("id", equalTo(2));
}
@Test
@DisplayName("Test thread-safe POST request")
@Description("Test POST request with per-request configuration")
public void testThreadSafePost() {
String requestBody = "{\"title\": \"Thread Safe Post\", \"body\": \"Test\", \"userId\": 1}";
given()
.config(RestAssuredConfig.config().httpClient(HttpClientConfig.httpClientConfig()
.setParam("http.socket.timeout", 5000)))
.header("Content-Type", "application/json")
.body(requestBody)
.log().all()
.when()
.post("/posts")
.then()
.log().all()
.statusCode(201)
.body("title", equalTo("Thread Safe Post"));
}
}
Explanation:
- Uses per-request
config()
to set timeouts, avoiding global state modifications. - Ensures thread-safety by isolating configurations to each test.
- Avoids static fields or shared resources that could cause race conditions.
Pro Tip: Always use per-request configurations or thread-local storage for shared resources in parallel tests to ensure thread-safety.
Example 4: Parallel Execution with Allure
Integrate parallel execution with Allure reporting.
Test Class: ParallelAllureTest
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("Parallel Execution with REST Assured")
public class ParallelAllureTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test parallel GET with Allure")
@Description("Test GET request with Allure reporting in parallel")
public void testParallelGetWithAllure() {
Response response = given()
.log().all()
.when()
.get("/posts/3")
.then()
.log().all()
.statusCode(200)
.body("id", equalTo(3))
.extract().response();
Allure.addAttachment("Post Response", "application/json", response.asString(), ".json");
}
@Test
@DisplayName("Test parallel POST with Allure")
@Description("Test POST request with Allure reporting in parallel")
public void testParallelPostWithAllure() {
String requestBody = "{\"title\": \"Allure Post\", \"body\": \"Test\", \"userId\": 1}";
Response response = given()
.header("Content-Type", "application/json")
.body(requestBody)
.log().all()
.when()
.post("/posts")
.then()
.log().all()
.statusCode(201)
.body("title", equalTo("Allure Post"))
.extract().response();
Allure.addAttachment("Post Creation Response", "application/json", response.asString(), ".json");
}
}
Explanation:
- Attaches API responses to Allure for each test, even in parallel execution.
- Allure handles parallel test reporting correctly with JUnit 5.
- Run
mvn clean test
andmvn allure:serve
to view the report.
Example 5: Configuring JUnit 5 for Parallel Execution
Use JUnit 5’s built-in parallel execution support for finer control.
Create a junit-platform.properties
file in src/test/resources
:
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=4
Test Class: JUnitParallelTest
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.hamcrest.Matchers.*;
@Feature("Parallel Execution with REST Assured")
public class JUnitParallelTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
@DisplayName("Test JUnit parallel GET")
@Description("Test GET request with JUnit 5 parallel execution")
public void testJUnitParallelGet() {
given()
.log().all()
.when()
.get("/posts/4")
.then()
.log().all()
.statusCode(200)
.body("id", equalTo(4));
}
@Test
@DisplayName("Test JUnit parallel comments")
@Description("Test comments request with JUnit 5 parallel execution")
public void testJUnitParallelComments() {
given()
.log().all()
.when()
.get("/comments/2")
.then()
.log().all()
.statusCode(200)
.body("id", equalTo(2));
}
}
Explanation:
junit-platform.properties
: Enables parallel execution with 4 fixed threads.- Complements Maven Surefire’s configuration for JUnit 5-specific control.
- Tests run concurrently, respecting thread-safety practices.
Integrating with CI/CD
Add parallel execution tests to a GitHub Actions pipeline for automation.
Create or update .github/workflows/ci.yml
:
name: REST Assured Parallel Execution 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 -Dparallel=classes -DthreadCount=4
- 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 parallel tests with
mvn clean test -Dparallel=classes -DthreadCount=4
. - Publishes Allure reports to GitHub Pages for visibility.
Tips for Beginners
- Ensure Test Independence: Avoid shared state (e.g., static fields, global configurations) to prevent race conditions.
- Limit Threads: Set a reasonable thread count based on your machine’s CPU cores and API rate limits.
- Monitor Resource Usage: Watch for network or CPU bottlenecks during parallel execution.
- Debug with Logs: Enable
log().all()
to trace issues in parallel test failures.
Troubleshooting Tip: If parallel tests fail unexpectedly, check for shared resource conflicts, reduce the thread count, and enable detailed logging to identify race conditions.
What’s Next?
This post enhances our REST Assured series by introducing Parallel Execution, a key technique for optimizing API test performance. 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.