TestNG Integration with REST Assured
REST Assured sends API requests and validates responses; TestNG decides which tests run, in what order, with what setup, and reports the results. Together they're a standard Java API test stack.
This guide shows how the two fit together with complete code — a shared request specification, lifecycle annotations, test groups, safe test ordering, data-driven execution, and parallel runs.
Who Does What
| Part | Responsibility |
|---|---|
| REST Assured | Build requests, send them, and validate status codes, headers and response bodies |
| TestNG | Test lifecycle, setup and teardown, grouping, ordering, data providers, parallel runs, listeners and reports |
testng.xml |
Defines which classes and groups run and how they run, including parallel execution and parameters |
| Maven | Manages dependencies and runs the test suite using commands such as mvn test |
A failed REST Assured assertion throws an AssertionError. TestNG catches the failure and marks that test as failed while the rest of the suite can continue.
Interview line:
"REST Assured handles requests and response validation; TestNG manages execution, setup and teardown, grouping, ordering and reporting."
Lifecycle Annotations
| Annotation | Runs | Typical Use in API Tests |
|---|---|---|
@BeforeSuite |
Once, before everything | Base URI, global configuration, logging on failure |
@BeforeClass |
Once per class | Get a token, build a request specification, create shared test data |
@BeforeMethod |
Before each test | Create fresh data required by a single test |
@Test |
Each test | Execute an API scenario with assertions |
@AfterMethod |
After each test | Delete data created by the test |
@AfterClass / @AfterSuite |
Once per class / at the end | Clean up shared data |
Use alwaysRun = true on cleanup methods so cleanup can still happen when a test fails.
For the complete TestNG reference, see TestNG Essentials.
A Complete Example
public abstract class BaseApiTest {
protected static RequestSpecification spec;
@BeforeSuite(alwaysRun = true)
public void configure() {
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
}
@BeforeClass(alwaysRun = true)
public void authenticate() {
String baseUri = System.getProperty(
"apiBaseUrl",
"https://api.qa.example.com"
);
String token = given()
.baseUri(baseUri)
.contentType(ContentType.JSON)
.body(
Map.of(
"username", "qa_user",
"password", System.getenv("API_PASSWORD")
)
)
.when()
.post("/auth/login")
.then()
.statusCode(200)
.extract()
.path("token");
spec = new RequestSpecBuilder()
.setBaseUri(baseUri)
.setContentType(ContentType.JSON)
.addHeader("Authorization", "Bearer " + token)
.build();
}
}
public class UserApiTest extends BaseApiTest {
private String userId;
@BeforeMethod(alwaysRun = true)
public void createUser() {
userId = given()
.spec(spec)
.body(
Map.of(
"name", "QA " + System.currentTimeMillis(),
"role", "viewer"
)
)
.when()
.post("/users")
.then()
.statusCode(201)
.extract()
.path("id");
}
@Test(groups = {"smoke", "regression"})
public void getUserReturnsCreatedUser() {
given()
.spec(spec)
.pathParam("id", userId)
.when()
.get("/users/{id}")
.then()
.statusCode(200)
.body("role", equalTo("viewer"));
}
@Test(groups = {"regression"})
public void updateUserRole() {
given()
.spec(spec)
.pathParam("id", userId)
.body(Map.of("role", "editor"))
.when()
.patch("/users/{id}")
.then()
.statusCode(200)
.body("role", equalTo("editor"));
}
@Test(groups = {"regression", "negative"})
public void getUnknownUserReturns404() {
given()
.spec(spec)
.pathParam("id", "does-not-exist")
.when()
.get("/users/{id}")
.then()
.statusCode(404);
}
@AfterMethod(alwaysRun = true)
public void deleteUser() {
if (userId != null) {
given()
.spec(spec)
.pathParam("id", userId)
.when()
.delete("/users/{id}");
}
}
}
The token is fetched once per class and stored in a reusable RequestSpecification, so every test can start with:
given().spec(spec)
Each test creates and deletes its own user, which helps keep the tests independent. Independent tests can execute in different orders and can be designed for parallel execution.
For more information about authentication, see REST Assured Authentication.
Controlling Order: Priority vs dependsOnMethods
A common pattern is to give each CRUD step a priority — login 1, create 2, update 3, delete 4 — and pass data between them using fields.
This can work, but it creates problems when an earlier test fails.
For example, if create fails, update and delete may still execute and produce confusing secondary failures. It can also make individual tests difficult to run independently.
| Approach | What It Does | When to Use |
|---|---|---|
priority |
Orders tests; lower values run first. It does not establish a dependency relationship. | Cosmetic ordering of otherwise independent tests |
dependsOnMethods |
Runs a test only when the named test has passed; otherwise the dependent test is skipped | A genuine workflow that must execute in sequence |
| Independent tests with setup | Each test creates the data it needs in @BeforeMethod |
Default approach for reliable, parallel-safe tests |
Using dependsOnMethods
@Test
public void createOrder() {
// Create order
}
@Test(dependsOnMethods = "createOrder")
public void payForOrder() {
// Pay for order
}
If createOrder() fails, payForOrder() is skipped rather than being reported as an unrelated failure.
Interview Line
"I keep API tests independent by creating their data in setup. For a real end-to-end flow I use
dependsOnMethodsrather than priority, so a failure in an early step skips the dependent tests instead of producing misleading failures."
Groups and testng.xml
TestNG groups make it possible to categorize API tests and execute only the tests required for a particular pipeline stage.
For example:
<suite name="API Suite" parallel="classes" thread-count="4">
<test name="Smoke">
<groups>
<run>
<include name="smoke"/>
<exclude name="slow"/>
</run>
</groups>
<packages>
<package name="com.shop.api.tests"/>
</packages>
</test>
</suite>
You can also run groups from Maven:
mvn test -Dgroups=smoke
Or execute regression tests while excluding slow tests:
mvn test -Dgroups=regression -DexcludedGroups=slow
A test can belong to multiple groups:
@Test(groups = {"smoke", "regression"})
public void getUser() {
// API test
}
A common pipeline strategy is:
- Smoke tests on every build
- Regression tests during scheduled execution
- Negative tests as part of broader validation
- Slow or expensive tests in dedicated jobs
For more information, see API Testing in CI/CD.
Parallel Runs
TestNG can execute API tests in parallel using settings such as:
<suite
name="API Suite"
parallel="classes"
thread-count="4">
Parallel execution can significantly reduce the execution time of a large API suite.
API tests generally have less overhead than browser-based UI tests, which can make parallel execution particularly useful.
However, parallel execution is safe only when tests don't interfere with one another.
Important Parallel Testing Rules
- Avoid sharing mutable test data between tests.
- Create test-specific data whenever possible.
- Use
ThreadLocalwhen thread-specific state is required. - Avoid static mutable variables that are changed by multiple tests.
- Keep authentication and request specifications appropriately isolated.
- Ensure cleanup does not delete data being used by another test.
A static field initialized in @BeforeClass can be shared across classes. This may be fine when the object is read-only, but it becomes risky if tests modify the shared object.
Data-Driven API Tests
TestNG's @DataProvider can be used to execute the same API test with multiple sets of data.
@DataProvider(name = "roles", parallel = true)
public Object[][] roles() {
return new Object[][] {
{"viewer", 200},
{"editor", 200},
{"superuser", 400}
};
}
@Test(dataProvider = "roles")
public void createUserWithRole(
String role,
int expectedStatus) {
given()
.spec(spec)
.body(
Map.of(
"name", "QA",
"role", role
)
)
.when()
.post("/users")
.then()
.statusCode(expectedStatus);
}
With parallel = true, the data-provider invocations can execute concurrently, provided the test data and application state are safe for parallel execution.
See the request-response flow inside each @Test step by step in the REST Assured Visualizer.
From Real Projects
I've tested APIs at the level of CRUD operations, HTTP methods and JSON path validation, and written automation in Java with TestNG. REST Assured brings those together in one Java library — which is why testers with Selenium and TestNG experience usually pick it up quickly. Start with one resource and automate its full CRUD cycle. Testsigma supports web, mobile and API test automation, so understanding APIs was part of understanding the product I was testing. Use TestNG groups to separate smoke and regression API tests.
FAQs
How does REST Assured integrate with TestNG?
REST Assured code runs inside TestNG @Test methods.
REST Assured handles:
- Request construction
- API calls
- Response extraction
- Response validation
TestNG handles:
- Test execution
- Setup and teardown
- Groups
- Ordering
- Data providers
- Parallel execution
- Reporting
When a REST Assured assertion fails, TestNG records the corresponding test as failed.
Which TestNG annotations are used most for API testing?
The commonly used annotations include:
@BeforeSuite— global configuration@BeforeClass— class-level setup such as authentication@BeforeMethod— per-test setup@Test— API test scenarios@AfterMethod— per-test cleanup@AfterClass— class-level cleanup@AfterSuite— suite-level cleanup
Why generate the token in @BeforeClass?
Generating a token in @BeforeClass avoids performing the login operation before every individual test.
This can:
- Reduce unnecessary API calls
- Reduce execution time
- Centralize authentication
- Make the test classes easier to maintain
However, token lifetime must be considered. If a token expires during a long-running suite, the framework may need a refresh strategy.
Priority or dependsOnMethods?
Prefer independent tests whenever possible.
Use priority when you simply need ordering for otherwise independent tests.
Use dependsOnMethods when there is a genuine business workflow where one operation cannot logically execute without another.
For example:
@Test
public void createOrder() {
// Create order
}
@Test(dependsOnMethods = "createOrder")
public void payForOrder() {
// Pay for order
}
If createOrder fails, the dependent test is skipped instead of producing another misleading failure.
How do you run only smoke tests?
Assign tests to a smoke group:
@Test(groups = "smoke")
public void healthCheck() {
// API test
}
Then execute:
mvn test -Dgroups=smoke
You can also configure the group in testng.xml.
Can API tests run in parallel?
Yes.
For example:
<suite
name="API Suite"
parallel="classes"
thread-count="4">
The most important requirement is that tests do not share mutable state or conflicting test data.