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.
No additional setup is required for 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 and CommentsTest) run in parallel, as configured in pom.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 and mvn 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.
Stay tuned for more testing tutorials!