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
: EnsurestestGetCreatedPost
runs only iftestCreatePost
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 withparallel="tests"
andthread-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
andmvn 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 intestng.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.