QA How-To
Data-Driven API Tests with REST Assured and TestNG DataProvider
Build REST Assured TestNG DataProvider tests with local API examples, success and error matrices, CSV fixtures, and Maven reports for reliable Java API testing.
24 min read | 3,024 words
TL;DR
Use TestNG @DataProvider to supply input and expected-result rows to REST Assured tests. This tutorial runs 13 local HTTP cases across book lookups, validation errors, stock combinations, and CSV fixtures, then checks Maven Surefire reports.
Key Takeaways
- Make each DataProvider row contain inputs and independently chosen expected outcomes.
- Use a local loopback server and per-class lifecycle for repeatable tutorial tests.
- Separate book success and error contracts when their assertions differ.
- Check zero stock as a successful quantity response when the API contract says so.
- Load reviewed CSV cases from test resources and reject malformed fixture rows.
- Inspect Surefire XML and confirm all 13 sample invocations ran.
REST Assured TestNG DataProvider tests let you send the same HTTP request pattern with several named inputs while TestNG reports each row as a separate invocation. This tutorial builds a small local Books API, then tests book lookup, validation errors, stock combinations, and CSV-backed cases. Every request stays on your machine, so the results do not depend on a public demo service.
You will write actual Java tests with REST Assured's request and response DSL and TestNG's @DataProvider. The important design choice is the row contract: each row must supply every input and expected outcome the test needs. That makes a failing row useful evidence instead of a mysterious parameter list. If the HTTP DSL is new to you, skim the REST Assured beginner tutorial before following the steps.
What You Will Build
- A Maven test project using Java 17, REST Assured, TestNG, and Maven Surefire.
- A loopback HTTP server with
GET /books/{id}andGET /stock/{sku}/{region}routes. - A book lookup provider that checks IDs, titles, JSON fields, and HTTP status.
- A separate negative-case provider for malformed and missing IDs.
- A stock matrix whose rows include both successful and error responses.
- A small CSV-backed provider, plus a command that emits CI-readable XML reports.
The service is intentionally narrow. You can inspect its rules in one file, run it without credentials, and see exactly why each expected value is correct. The provider pattern transfers to an existing API after you replace the local base URI and derive expectations from that API's contract.
Prerequisites
Use JDK 17 and Maven 3.9.9 for these commands. The dependency versions below are published versions: REST Assured 6.0.1, TestNG 7.12.0, Maven Compiler Plugin 3.14.1, and Maven Surefire Plugin 3.6.0. The REST Assured getting-started guide lists 6.0.1, and its release notes set a Java 17 minimum. TestNG releases list 7.12.0, while the Maven Surefire documentation shows the 3.6.0 test setup. If your organization uses a different approved set, pin its tested versions together rather than copying a speculative future version. Check your local tools first:
java -version
mvn -version
The first command should report Java 17 or newer; the tutorial targets Java 17 bytecode. Maven should report 3.9.9 for an exact match to this example. A later Maven 3.9 release may work, but record its value when sharing a failure. You need network access to your configured Maven repository only for the first dependency download. The HTTP tests themselves use a local ephemeral port and no hosted account.
Create a clean directory outside your existing application repository if you want a throwaway exercise. The project structure will be pom.xml, src/test/java/example/, and src/test/resources/. Do not put these tutorial Java files inside the QAJobFit React app. They are sample files for the standalone Maven project you build while reading.
Step 1: Create the REST Assured TestNG DataProvider Project
Make the directory and save this complete pom.xml at its root. Surefire runs test classes named *Test.java; the compiler plugin fixes the Java language level. The dependencies have test scope because neither REST Assured nor TestNG belongs in a production artifact for this exercise.
mkdir rest-assured-provider-demo
cd rest-assured-provider-demo
mkdir -p src/test/java/example src/test/resources
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>rest-assured-provider-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>6.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.12.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.6.0</version>
</plugin>
</plugins>
</build>
</project>
Verify with mvn -q test. At this point, expect a successful build with no test classes. That result proves Maven can resolve the dependencies and compile the empty project; it does not prove an API assertion yet. If dependency resolution fails, inspect your configured repository or mirror before changing the Java code. Avoid treating a build that ran zero tests as evidence that the suite is complete.
This example uses TestNG directly through the Maven test phase. You do not need a testng.xml file for ordinary discovered *Test classes. A suite XML file becomes useful when you need explicit groups, listeners, or ordering. For a closer look at the framework's annotations, see the TestNG tutorial for beginners.
Step 2: Start a Deterministic Local API
Save the following as src/test/java/example/BookServer.java. Java's built-in HttpServer binds to loopback on port zero, which asks the operating system for a free port. Each test class will get its own server instance. The handler returns two known books, rejects malformed IDs, and exposes a small stock matrix. No database or external service can change its responses between runs.
package example;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public final class BookServer implements AutoCloseable {
private final HttpServer server;
public BookServer() throws IOException {
server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/books", this::books);
server.createContext("/stock", this::stock);
server.start();
}
public String baseUri() {
return "http://127.0.0.1:" + server.getAddress().getPort();
}
private void books(HttpExchange exchange) throws IOException {
if (!exchange.getRequestMethod().equals("GET")) {
send(exchange, 405, "{\"error\":\"method not allowed\"}");
return;
}
String path = exchange.getRequestURI().getPath();
if (!path.matches("/books/[0-9]+")) {
send(exchange, 400, "{\"error\":\"invalid book id\"}");
return;
}
String id = path.substring("/books/".length());
switch (id) {
case "1" -> send(exchange, 200, "{\"id\":1,\"title\":\"Clean Code\"}");
case "2" -> send(exchange, 200, "{\"id\":2,\"title\":\"Refactoring\"}");
default -> send(exchange, 404, "{\"error\":\"book not found\"}");
}
}
private void stock(HttpExchange exchange) throws IOException {
if (!exchange.getRequestMethod().equals("GET")) {
send(exchange, 405, "{\"error\":\"method not allowed\"}");
return;
}
String[] parts = exchange.getRequestURI().getPath().split("/");
if (parts.length != 4 || !parts[2].matches("[A-Z]+")
|| !(parts[3].equals("US") || parts[3].equals("GB"))) {
send(exchange, 400, "{\"error\":\"invalid stock request\"}");
return;
}
String sku = parts[2];
String region = parts[3];
Map<String, Integer> quantities = Map.of(
"PEN:US", 8, "PEN:GB", 0, "PAD:US", 3);
Integer available = quantities.get(sku + ":" + region);
if (available == null) {
send(exchange, 404, "{\"error\":\"stock not found\"}");
return;
}
send(exchange, 200, "{\"sku\":\"" + sku + "\",\"region\":\""
+ region + "\",\"available\":" + available + "}");
}
private void send(HttpExchange exchange, int status, String json)
throws IOException {
byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
exchange.sendResponseHeaders(status, bytes.length);
try (var output = exchange.getResponseBody()) {
output.write(bytes);
}
}
@Override
public void close() {
server.stop(0);
}
}
Verify compilation with mvn -q test-compile. Expect a zero exit status and a target/test-classes/example/BookServer.class file. This check catches a pasted quote, missing import, or wrong Java release before you add assertions. It does not start the server because no TestNG class invokes the constructor yet.
The server deliberately accepts a tiny set of JSON-free input paths. That keeps the tutorial centered on DataProvider behavior rather than request-body parsing. In a real suite, the expected status and fields should come from an API specification and product rules. Do not update expectations merely because a broken server returned a different value.
Step 3: Parameterize Successful Book Lookup
First save src/test/java/example/ApiFixture.java. TestNG runs the inherited @BeforeClass method before a subclass's test methods and @AfterClass afterward. The fixture exposes the chosen port to the test and closes the local server even if an assertion fails. Keeping the server as an instance field avoids a shared global base URI that could be overwritten by another class.
package example;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
public abstract class ApiFixture {
protected BookServer api;
@BeforeClass
public void startApi() throws Exception {
api = new BookServer();
}
@AfterClass(alwaysRun = true)
public void stopApi() {
if (api != null) {
api.close();
}
}
}
Now save src/test/java/example/BookLookupTest.java. The DataProvider returns an Object[][]: each inner array is one invocation, and its three values match the method's String, int, and String parameters in order. The test uses pathParam so the URL template remains readable as cases change.
package example;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class BookLookupTest extends ApiFixture {
@DataProvider(name = "knownBooks")
public Object[][] knownBooks() {
return new Object[][] {
{"1", 1, "Clean Code"},
{"2", 2, "Refactoring"}
};
}
@Test(dataProvider = "knownBooks")
public void returnsKnownBook(String id, int expectedId, String expectedTitle) {
given()
.baseUri(api.baseUri())
.pathParam("id", id)
.when()
.get("/books/{id}")
.then()
.statusCode(200)
.header("Content-Type", startsWith("application/json"))
.body("id", equalTo(expectedId))
.body("title", equalTo(expectedTitle));
}
}
Verify with mvn -q -Dtest=BookLookupTest test. Expect two passing TestNG invocations in target/surefire-reports. Maven may summarize one test class while the XML contains each parameterized invocation; inspect the report if the console's quiet output hides details. If the test reports no tests, confirm the filename ends in Test.java and that it is under src/test/java.
The status, media type, ID, and title are independent assertions. A server could return 200 with the wrong book; a status-only check would miss it. The row's expectedId is numeric because REST Assured extracts JSON numbers as values suitable for this equality check. The title provides a second field so an accidental copy of the wrong record does not pass. The given-when-then REST Assured guide explains the DSL's request and assertion stages in more depth.
Step 4: Give Error Cases Their Own Provider
Save src/test/java/example/BookErrorTest.java. The malformed ID abc should be a 400 because it cannot satisfy the route's numeric ID format. The numeric ID 999 is well formed but absent, so it should be a 404. These are different contracts and deserve distinct expected messages even though the request pattern is the same.
package example;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class BookErrorTest extends ApiFixture {
@DataProvider(name = "badLookups")
public Object[][] badLookups() {
return new Object[][] {
{"abc", 400, "invalid book id"},
{"999", 404, "book not found"}
};
}
@Test(dataProvider = "badLookups")
public void rejectsBadLookup(String id, int expectedStatus, String expectedError) {
given()
.baseUri(api.baseUri())
.pathParam("id", id)
.when()
.get("/books/{id}")
.then()
.statusCode(expectedStatus)
.body("error", equalTo(expectedError));
}
}
Verify with mvn -q -Dtest=BookErrorTest test. Expect two passing invocations. Change the expected status for abc to 404 temporarily and rerun: only that row should fail, showing that TestNG preserves the case boundary. Restore 400 afterward. This deliberate mutation is a stronger check than simply seeing a green build, because it proves the assertion detects a contract change.
Do not merge success and failure rows into one test that skips body checks for every non-200 status. That structure can silently accept the wrong error representation. A focused negative provider makes the expected message explicit for each route condition. For a production API, also check documented error codes, correlation IDs, and whether invalid requests leave state unchanged. The local server is read-only, so it has no write side effect to inspect.
Step 5: Cover a Multi-Dimensional Stock Matrix
Save src/test/java/example/StockMatrixTest.java. This provider varies SKU and region together. Three known combinations return quantities, including zero stock, which is still a successful 200 response. An unsupported region returns 400; an unknown SKU in a supported region returns 404. The expected quantity is nullable because it has no meaning for an error body.
package example;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import io.restassured.response.Response;
public class StockMatrixTest extends ApiFixture {
@DataProvider(name = "stockCases")
public Object[][] stockCases() {
return new Object[][] {
{"PEN", "US", 200, 8, null},
{"PEN", "GB", 200, 0, null},
{"PAD", "US", 200, 3, null},
{"PEN", "FR", 400, null, "invalid stock request"},
{"BAG", "US", 404, null, "stock not found"}
};
}
@Test(dataProvider = "stockCases")
public void checksStock(String sku, String region, int status,
Integer expectedAvailable, String expectedError) {
Response response = given()
.baseUri(api.baseUri())
.pathParam("sku", sku)
.pathParam("region", region)
.when()
.get("/stock/{sku}/{region}");
response.then().statusCode(status);
if (status == 200) {
response.then()
.body("sku", equalTo(sku))
.body("region", equalTo(region))
.body("available", equalTo(expectedAvailable));
} else {
response.then().body("error", equalTo(expectedError));
}
}
}
Verify with mvn -q -Dtest=StockMatrixTest test. Expect five passing invocations. The zero-quantity row is important: stock status and stock quantity are different concepts. A client may correctly receive 200 with available: 0; a test that assumes zero means 404 would encode the wrong business rule.
| Case | Expected HTTP result | Assertion that matters |
|---|---|---|
| Known SKU, supported region, positive stock | 200 | Correct SKU, region, and quantity |
| Known SKU, supported region, zero stock | 200 | Quantity is exactly zero |
| Known SKU, unsupported region | 400 | Validation error body |
| Unknown SKU, supported region | 404 | Missing-stock error body |
A single provider is useful here because every row represents the same stock endpoint and the same response decision tree. If the success and error branches gain many unrelated assertions, split them into separate tests and providers, as the book examples do. Keep expected values in the row rather than calculating them from the response. Otherwise the test can reproduce the service's bug and still pass.
REST Assured returns a Response so the test can assert the status before selecting the appropriate body contract. It does not call asString() or parse an error as a success object. When debugging a failing row, add targeted request and response logging rather than printing every request in every build; the REST Assured logging filters guide covers that setup.
Step 6: Move Stable Cases into a CSV Resource
A provider can load reviewed examples from a file when product owners or testers maintain a larger matrix. Save src/test/resources/book-cases.csv with this simple four-column data. Blank title or error cells mean that field does not apply to the row.
id,status,title,error
1,200,Clean Code,
2,200,Refactoring,
abc,400,,invalid book id
999,404,,book not found
Save src/test/java/example/BookCsvTest.java. The resource is read through the classpath, so Maven and IDE runs find the same file. This tiny parser deliberately supports only this controlled, comma-free sample; use a CSV library for quoted commas, embedded newlines, or externally supplied files.
package example;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class BookCsvTest extends ApiFixture {
@DataProvider(name = "bookRows")
public Object[][] bookRows() throws Exception {
var resource = Objects.requireNonNull(
getClass().getResourceAsStream("/book-cases.csv"));
try (var reader = new BufferedReader(
new InputStreamReader(resource, StandardCharsets.UTF_8))) {
return reader.lines().skip(1).map(line -> {
String[] cells = line.split(",", -1);
if (cells.length != 4) {
throw new IllegalArgumentException("Expected four CSV columns: " + line);
}
return new Object[] {
cells[0], Integer.parseInt(cells[1]), cells[2], cells[3]
};
}).toArray(Object[][]::new);
}
}
@Test(dataProvider = "bookRows")
public void checksBookRow(String id, int status, String title, String error) {
var response = given()
.baseUri(api.baseUri())
.pathParam("id", id)
.when()
.get("/books/{id}");
response.then().statusCode(status);
if (status == 200) {
response.then().body("title", equalTo(title));
} else {
response.then().body("error", equalTo(error));
}
}
}
Verify with mvn -q -Dtest=BookCsvTest test. Expect four passing invocations. Delete one comma from a data row temporarily: TestNG should fail during provider evaluation with Expected four CSV columns. Restore the row before continuing. That guard keeps a malformed fixture from turning into a misleading HTTP failure.
External data is useful when examples are stable and a reviewer can understand each row without tracing generated values. Keep secrets, access tokens, and live customer records out of the CSV. In a production system, classify test inputs by contract risk, such as missing field, boundary value, unsupported region, and ownership mismatch. Do not generate a giant Cartesian product unless every combination has a distinct reason to exist. Compare this approach with Postman data-driven testing if your team also maintains collection-based suites.
Step 7: Verify REST Assured TestNG DataProvider Reports
Run every class through the same command your CI job will use. Surefire's test goal compiles test code, discovers the classes, executes each DataProvider row, and writes XML and text reports. The suite in this tutorial has two book success cases, two book errors, five stock cases, and four CSV cases: 13 invocations in total.
mvn -B -ntp test
find target/surefire-reports -name 'TEST-*.xml' -print
Verify that Maven exits successfully, reports 13 tests with zero failures and errors, and prints report paths. If you ran a class-specific command earlier, the final full run replaces partial reports with results for the complete discovered suite. Open an XML file to find test names, durations, and any failure stack trace. CI systems can collect target/surefire-reports/TEST-*.xml as test artifacts; publishing mechanics depend on your platform.
For a controlled failure demonstration, change the PEN:GB expected quantity from 0 to 1 and run mvn -B -ntp -Dtest=StockMatrixTest test. The row should fail at the quantity assertion while the other four rows pass. Restore 0 and rerun the full suite. This confirms that the matrix checks a real value, not merely that an HTTP call completed.
Keep this local example serial. TestNG supports @DataProvider(parallel = true), but a provider should only use that after its client, data, server, and reporting are safe under concurrent invocations. The current handlers are read-only, so they are simple to reason about; a shared account or mutable test record would change that risk. Parallelism can shorten a slow suite while making order-dependent fixtures and rate limits harder to diagnose. Measure the actual bottleneck before turning it on.
Troubleshooting
Problem: Maven says there are no tests -> Confirm files end in Test.java, sit under src/test/java/example, and contain TestNG @Test methods. Run mvn -B -ntp test from the directory containing pom.xml, then inspect the Surefire report directory.
Problem: UnsupportedClassVersionError appears -> Check java -version and mvn -version. The example compiles for Java 17 and REST Assured 6 requires Java 17 or newer. Make your shell and IDE use a compatible JDK, then rerun mvn clean test in the standalone sample project.
Problem: TestNG reports a DataProvider parameter mismatch -> Count values in every Object[] and compare their order and types with the @Test method signature. For example, the stock method expects five values and expectedAvailable accepts Integer so a null error-case quantity is legal.
Problem: a 400 row returns 404 instead -> Check the exact path sent to the local server. abc fails the numeric ID format and should be 400; 999 is numeric but missing and should be 404. A real service may define different statuses, so follow its documented contract rather than these sample rules.
Problem: the CSV provider cannot find its resource -> Put book-cases.csv directly in src/test/resources, not beside the Java class. Maven copies resources to the test classpath; the leading slash in getResourceAsStream("/book-cases.csv") looks at that classpath root.
Problem: tests fail only when parallel execution is enabled -> Disable parallel = true while diagnosing shared mutable data, static REST Assured settings, and server lifetime. Give each invocation its own data and avoid changing a global base URI. Re-enable concurrency only after you can repeat a full run reliably.
Where To Go Next
Replace the local BookServer with a client pointed at a disposable test environment. Move its base URI to an environment property, keep authentication out of fixture CSV files, and have each test create or identify data it owns. Separate contract assertions from cleanup so a failed response still triggers teardown. The REST Assured request and response specification guide shows how to centralize repeatable request settings without hiding what each test checks.
Add schema validation when shape changes are a significant risk, and keep field-level assertions for values that matter to a specific row. The REST Assured JSON schema validation guide covers that layer. If you are preparing to discuss this design in an interview, use the API testing interview questions to practice explaining why one malformed ID gets 400 while one missing numeric ID gets 404.
Interview Questions and Answers
Q: What does a TestNG DataProvider return?
A common form is Object[][], where each inner array supplies arguments for one invocation of a @Test(dataProvider = "...") method. TestNG also accepts iterator-based provider forms when rows should be produced lazily. The parameter count and assignable types must match the test signature.
Q: Why does the stock provider contain expected values?
Expected quantities and errors come from the contract, not from the response under test. Putting them in the row makes each scenario reviewable. If a test derives its expectation from the returned JSON, the same wrong value may appear on both sides of the assertion.
Q: Should every status be in one provider?
Only when rows exercise one coherent behavior and share an understandable assertion flow. Book success and error cases are split because their response contracts differ. The stock matrix combines them because its branch is short and the row explicitly carries status, quantity, and error.
Q: How do you make a failing parameter row easy to diagnose?
Keep row fields meaningful, assert status before body fields, and include the request values in the test report. For a large matrix, use a case object with a descriptive toString() or an appropriate TestNG naming mechanism. Log request and response details on failure while masking credentials.
Q: What breaks if a provider runs in parallel?
Shared mutable records, a static base URI, non-thread-safe fixtures, and a server stopped while another invocation is active can produce intermittent failures. The provider annotation can enable parallel execution, but the test data and lifecycle must support it first. Start serial and prove isolation before increasing concurrency.
Q: Why keep the sample server local?
It removes external network, account, and staging-data variation while teaching the runner and assertion pattern. Its rules are visible and deterministic, so a failing test points to code or setup you can inspect. Production confidence still requires running equivalent contract cases against the real service.
Common Mistakes
- Returning four values from a provider row when the test method declares five parameters.
- Reusing one expected error string for both malformed and missing resources.
- Asserting only the status and missing a wrong ID, title, or quantity.
- Building expected values from the actual response rather than from the contract.
- Treating zero stock as an absent resource without a product rule that says so.
- Putting authentication secrets or production customer data in CSV fixtures.
- Enabling parallel rows while tests share mutable state or global REST Assured configuration.
- Trusting a green Maven build before confirming that Surefire discovered any tests.
The fastest review is to read each row as a sentence: given this ID, SKU, or region, the API must return this exact status and body value. If a row cannot be read that way, rename or split its fields before adding more cases.
Conclusion
REST Assured TestNG DataProvider tests work when a provider supplies a clear, typed contract row and the test asserts the observable HTTP result for that row. You now have a local API, separate success and failure providers, a stock matrix, a classpath CSV example, and a full Maven report command.
Run the suite once more after restoring any deliberate mutations. Then port one real endpoint at a time, keeping its expected values independent of the implementation and its test data isolated from other runs.
Interview Questions and Answers
How does TestNG map DataProvider rows to a test method?
TestNG invokes the method once for each Object[] row and passes row entries in order. The count and assignable types must match the method signature. I keep each row small and put the expected status and values beside the request inputs so a reviewer can understand a failure.
How would you design data for a REST Assured endpoint with success and error cases?
I start from the endpoint contract and list behaviorally distinct conditions. A success row has expected fields such as ID or quantity; an error row has the documented status and error code or message. I split providers when the two response shapes need substantially different assertions.
Why should a test not calculate its expected value from the response?
That makes the response both the subject and the oracle. If the service returns a wrong quantity, a derived expectation can reproduce the same mistake and pass. I store expectations in the provider or compute them from an independent contract fixture.
How would you prevent parameterized API tests from affecting each other?
I use unique records per invocation, capture returned IDs, and clean up in a reliable finalizer. For a local read-only service, a per-class server instance is enough. For shared staging, I also avoid assertions about global collection counts or the next generated ID.
When is an Iterator-based DataProvider useful?
An iterator can produce cases lazily when a provider has many rows or expensive case generation. It does not automatically make an external CSV reader safe or close resources correctly. I use it when memory or generation cost justifies the extra lifecycle complexity.
What is the difference between a 400 and a 404 in this book example?
The string abc violates the numeric ID format, so the sample API returns 400. The numeric ID 999 is syntactically valid but has no record, so it returns 404. I test those separately because they reveal different validation and lookup behavior.
What would you inspect when a DataProvider case passes locally but fails in CI?
I compare Java and dependency versions, inspect the Surefire XML for the exact row, and check the request URI and response body. Then I look for shared test data, environment configuration, or parallel execution differences. I avoid increasing timeouts until the failure shows a real timing issue.
Frequently Asked Questions
How do I connect a TestNG DataProvider to a REST Assured test?
Annotate a provider method with @DataProvider(name = "cases") and reference that name in @Test(dataProvider = "cases"). Each Object[] row must match the test method's parameter order and types. Use those parameters in REST Assured request construction and expected-response assertions.
Can a DataProvider contain both positive and negative API cases?
Yes, when the rows still describe one coherent endpoint behavior. Carry the expected status and relevant body fields in every row, then assert the proper success or error contract. Split providers when branching makes the test difficult to understand.
Why does my DataProvider test run zero times?
Check that the provider is referenced by the same name, its returned array is nonempty, and Surefire discovers the test class. In this example, the class must end in Test.java and live under src/test/java. Inspect the Maven report rather than relying only on a green exit code.
Can REST Assured use TestNG DataProvider with CSV data?
Yes. Read a test resource in the provider method and convert each reviewed row to an Object[] with the expected parameter types. The sample parser supports only simple comma-free cells; use a proper CSV parser for quoted or externally maintained data.
Should I set @DataProvider(parallel = true) for API tests?
Use parallel rows only after request clients, fixtures, test records, and the target API can handle concurrent invocations. Shared mutable data and global REST Assured settings can create intermittent failures. Start serial, then measure whether concurrency helps.
What version of Java does REST Assured 6 require?
REST Assured 6 requires Java 17 or newer. This tutorial compiles to Java 17 bytecode and uses REST Assured 6.0.1. Verify both the shell's java version and Maven's selected Java runtime before investigating test failures.
Where do TestNG DataProvider results appear in Maven?
Maven Surefire writes test result files under target/surefire-reports. Run mvn test, confirm the test count, and collect TEST-*.xml files in CI. A parameterized invocation should appear as an individual test result in the report.
Related Guides
- API Testing with pytest and requests: Step-by-Step Tutorial
- Generating API tests from OpenAPI with AI (2026)
- How to Build a REST Assured API framework from scratch (2026)
- Idempotency and retries in API tests (2026)
- REST Assured Query and Path Parameters Tutorial
- REST Assured request and response spec: A Practical Guide (2026)