Introduction

In our REST Assured series, we’ve explored topics like Request and Response Logging, Hamcrest Matchers, and JSON Schema Validation, building a strong foundation for API testing with JUnit. Now, we’ll integrate REST Assured with TestNG, a powerful testing framework that offers advanced features like test dependencies, parallel execution, and data-driven testing. This guide demonstrates how to use REST Assured with TestNG for robust API tests, with practical examples and best practices. It’s designed for beginners and experienced developers, ensuring you can leverage TestNG’s capabilities effectively.

Key Point: Integrating REST Assured with TestNG enhances API testing with features like test prioritization, data providers, and parallel execution, ideal for complex test suites.

Why Use TestNG with REST Assured?

TestNG (Test Next Generation) is a testing framework inspired by JUnit but with additional features tailored for modern testing needs. Key advantages for API testing include:

  • Annotations: Flexible annotations like @BeforeClass, @Test, and @AfterMethod for setup and teardown.
  • Data-Driven Testing: @DataProvider for running tests with multiple data sets.
  • Test Dependencies: Define test execution order with dependsOnMethods.
  • Parallel Execution: Run tests concurrently to reduce execution time.
  • Reporting: Built-in HTML reports and integration with Allure for detailed insights.

REST Assured works seamlessly with TestNG, using the same fluent API for requests and assertions. We’ll use https://jsonplaceholder.typicode.com for examples, testing endpoints like /posts to demonstrate TestNG features.

Setting Up REST Assured with TestNG

We’ll set up a Maven project with REST Assured, TestNG, and Allure for reporting. The testng dependency replaces JUnit, and we’ll include allure-testng for reporting.

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



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

    
        11
        2.27.0
        1.9.22
    

    
        
            io.rest-assured
            rest-assured
            5.4.0
            test
        
        
            org.testng
            testng
            7.10.2
            test
        
        
            org.hamcrest
            hamcrest
            2.2
            test
        
        
            com.fasterxml.jackson.core
            jackson-databind
            2.15.2
            test
        
        
            io.qameta.allure
            allure-testng
            ${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. Create a testng.xml file to configure test suites (shown later in Example 5).

Using REST Assured with TestNG

Let’s explore how to write API tests with REST Assured and TestNG, leveraging its advanced features.

Example 1: Basic Test with TestNG Annotations

Write a simple API test using TestNG annotations for setup and test execution.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("REST Assured with TestNG")
public class BasicTestNGTest {

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

    @Test(description = "Test fetching a single post")
    @Description("Test fetching a single post with TestNG")
    public void testGetPost() {
        given()
            .log().all()
        .when()
            .get("/posts/1")
        .then()
            .log().all()
            .statusCode(200)
            .body("id", equalTo(1))
            .body("title", notNullValue());
    }
}

Explanation:

  • @BeforeClass: Sets up the base URI before tests run.
  • @Test(description = "..."): Defines a test case with a description, similar to JUnit’s @DisplayName.
  • REST Assured assertions use Hamcrest Matchers, as in previous posts.
Important: Use TestNG annotations like @BeforeClass and @AfterClass for setup and teardown to keep tests organized.

Example 2: Data-Driven Testing with @DataProvider

Use a @DataProvider to test multiple post IDs.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("REST Assured with TestNG")
public class DataDrivenTest {

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

    @DataProvider(name = "postIds")
    public Object[][] providePostIds() {
        return new Object[][] {
            {1},
            {2},
            {3}
        };
    }

    @Test(dataProvider = "postIds", description = "Test fetching posts with different IDs")
    @Description("Test fetching posts with different IDs using DataProvider")
    public void testGetPosts(int postId) {
        given()
            .log().all()
        .when()
            .get("/posts/{id}", postId)
        .then()
            .log().all()
            .statusCode(200)
            .body("id", equalTo(postId))
            .body("userId", greaterThan(0));
    }
}

Explanation:

  • @DataProvider: Supplies test data (post IDs) to the test method.
  • @Test(dataProvider = "postIds"): Runs the test for each data set.
  • Each test iteration logs the request and response for debugging.

Example 3: Test Dependencies

Use dependsOnMethods to ensure a POST request succeeds before fetching the created resource.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("REST Assured with TestNG")
public class DependencyTest {

    private int createdPostId;

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

    @Test(description = "Test creating a post")
    @Description("Test creating a post")
    public void testCreatePost() {
        String requestBody = "{\"title\": \"Test Post\", \"body\": \"This is a test\", \"userId\": 1}";

        createdPostId = given()
            .header("Content-Type", "application/json")
            .body(requestBody)
            .log().all()
        .when()
            .post("/posts")
        .then()
            .log().all()
            .statusCode(201)
            .body("id", notNullValue())
            .extract().path("id");

        // For demonstration, assume the ID is returned (jsonplaceholder echoes the request)
    }

    @Test(dependsOnMethods = "testCreatePost", description = "Test fetching the created post")
    @Description("Test fetching the created post")
    public void testGetCreatedPost() {
        given()
            .log().all()
        .when()
            .get("/posts/{id}", createdPostId)
        .then()
            .log().all()
            .statusCode(200)
            .body("title", equalTo("Test Post"));
    }
}

Explanation:

  • dependsOnMethods: Ensures testGetCreatedPost runs only if testCreatePost passes.
  • The created post ID is stored in a class variable for reuse.
  • Note: jsonplaceholder.typicode.com simulates creation but doesn’t persist data, so the GET request may not return the created post in production scenarios.
Pro Tip: Use test dependencies sparingly to avoid tightly coupled tests, ensuring maintainability.

Example 4: Parallel Execution with TestNG

Configure parallel test execution using a testng.xml file.

Create testng.xml in the project root:




    
        
            
        
    

Test code:


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("REST Assured with TestNG")
public class ParallelTest {

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

    @Test(description = "Test fetching post 1")
    @Description("Test fetching post 1")
    public void testGetPost1() {
        given()
            .log().all()
        .when()
            .get("/posts/1")
        .then()
            .log().all()
            .statusCode(200)
            .body("id", equalTo(1));
    }

    @Test(description = "Test fetching post 2")
    @Description("Test fetching post 2")
    public void testGetPost2() {
        given()
            .log().all()
        .when()
            .get("/posts/2")
        .then()
            .log().all()
            .statusCode(200)
            .body("id", equalTo(2));
    }
}

Explanation:

  • testng.xml: Configures parallel execution with parallel="tests" and thread-count="2".
  • Both tests run concurrently, reducing execution time.
  • Logging helps verify parallel execution behavior.

Example 5: Logging and Allure Integration

Integrate logging and Allure reporting with TestNG.


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.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("REST Assured with TestNG")
public class AllureTestNGTest {

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

    @Test(description = "Test fetching a post with Allure logging")
    @Description("Test fetching a post with Allure logging")
    public void testAllureLogging() {
        Response response = given()
            .log().all()
        .when()
            .get("/posts/1")
        .then()
            .log().all()
            .statusCode(200)
            .body("id", equalTo(1))
            .extract().response();

        Allure.addAttachment("Response Body", "application/json", response.asString(), ".json");
    }
}

Explanation:

  • Allure.addAttachment: Attaches the response body to the Allure report.
  • log().all(): Logs request and response details for debugging.
  • Run mvn clean test and mvn allure:serve to view the report.

Integrating with CI/CD

Add TestNG-based tests to a GitHub Actions pipeline for automation.

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


name: REST Assured TestNG 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 -Dsurefire.suiteXmlFiles=testng.xml

      - 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:

  • -Dsurefire.suiteXmlFiles=testng.xml: Runs tests defined in testng.xml.
  • Publishes Allure reports to GitHub Pages for visibility.

Tips for Beginners

  • Use Descriptive Annotations: Add description to @Test for clear test reports.
  • Leverage Data Providers: Use @DataProvider for repetitive tests with varying inputs.
  • Configure Parallelism Carefully: Test parallel execution locally to avoid resource conflicts.
  • Integrate with Allure: Attach logs and responses to Allure for detailed reports.
Troubleshooting Tip: If TestNG tests fail, enable log().all() to inspect request/response details and check TestNG reports for execution order or dependency issues.

What’s Next?

This post enhances our REST Assured series by integrating TestNG, unlocking advanced testing features for API automation. 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!