You've written a great test for one set of inputs. Now you want to run it against twenty. Data-driven testing is the technique of feeding a list of input rows into a single request — Postman runs the request once per row, substituting the row's values into the URL, body, and tests. It's the difference between testing a login with one happy path and testing it against a dozen valid, invalid, edge-case, and security-flavoured combinations in seconds. This lesson covers CSV and JSON data formats, the Collection Runner's data feature, dynamic assertions per row, and the same pattern via Newman on the command line.
What data-driven testing is for
A login endpoint deserves at least these inputs:
A valid admin user with the right password.
A valid regular user with the right password.
A valid email with a wrong password.
A nonexistent email.
An empty email.
An email with SQL-injection characters.
An over-length password.
A password with leading/trailing whitespace.
Writing eight separate requests for those is wasteful — they're the same call with different inputs and different expected outcomes. Data-driven testing lets you write one request, one Tests script, and run it eight times.
The same pattern works for:
Form validation (every field empty, too short, too long, special characters).
Search endpoints (different queries, different expected counts).
Boundary value testing (min, max, just outside each).
Permissions matrices (each role × each endpoint).
Localised content (one request per locale).
CSV data file format
The simplest format. The first row is the header (column names become variable names); each subsequent row is one iteration's data:
email,password,expectedStatus,expectedTokenadmin@test.com,AdminPass123,200,trueuser@test.com,UserPass123,200,trueadmin@test.com,WrongPass,401,falsenope@test.com,AnyPass,401,false,AnyPass,400,falseadmin@test.com,,400,falseadmin@test.com,'; DROP TABLE--,400,false
Save that as login-test-data.csv somewhere on disk. Seven rows × four columns = seven iterations of one request.
CSV gotchas:
Quotes around values that contain commas."hello, world". Without the quotes, the comma is a column separator.
No trailing whitespace in column headers — email and email are different variable names.
Booleans and numbers come in as strings.true is the string "true", not the boolean true. Convert in the Tests script if the type matters.
JSON data file format
JSON is more expressive — values keep their types, and you can nest objects:
The file is a top-level array; each element is one iteration. Keys become variable names. Use JSON when you need numeric or boolean types preserved, or when individual fields are themselves complex objects.
Using data variables in requests
Once Postman knows about your data file (via the Collection Runner), data variables work like any other: {{email}}, {{password}}, {{expectedStatus}}. Use them anywhere — URL, headers, body:
The trick is making your assertions adapt to each row. The Tests tab can read the iteration's data via pm.iterationData:
const expectedStatus = pm.iterationData.get("expectedStatus");const expectedToken = pm.iterationData.get("expectedToken") === "true";pm.test(`Status should be ${expectedStatus}`, () => { pm.response.to.have.status(parseInt(expectedStatus, 10));});if (expectedToken) { pm.test("Successful login returns a token", () => { pm.expect(pm.response.json()).to.have.property("token"); });} else { pm.test("Failed login does NOT return a token", () => { pm.expect(pm.response.json()).to.not.have.property("token"); });}
Two things to notice:
The test name uses a template literal (`Status should be ${expectedStatus}`) so the report shows what was expected for that iteration — Status should be 200 for row 1, Status should be 401 for row 3.
expectedStatus is parsed as an integer because CSV values come in as strings. JSON data files preserve types and don't need the parseInt.
Running with the Collection Runner
The end-to-end workflow:
Step 1 of 5
Open the Runner
Right-click your collection (or folder) → Run folder. The Collection Runner opens in a new view.
Once the run finishes, you get a per-iteration report — every row, every assertion, pass or fail. Click any iteration to see the actual request that went out and the response that came back.
Common iteration patterns
Skip a row conditionally. Use the Tests tab on the previous iteration to call postman.setNextRequest(null) if you want to abort early — useful when a setup step fails:
if (pm.response.code !== 200) { postman.setNextRequest(null); // Stop the run}
Run the same iteration twice. Postman's pm.iterationData is read-only per iteration; if you want a single row to drive multiple requests, build a folder with all those requests and run the folder.
Per-iteration setup. A pre-request script can read pm.iterationData too — handy for computing values from the row's inputs:
Once your collection runs cleanly in the Runner, the CLI version is one line. Newman is Postman's command-line runner (covered in depth in Chapter 5):
newman run "JSONPlaceholder API Tests.postman_collection.json" \ -e JSONPlaceholder.postman_environment.json \ -d login-test-data.csv \ --reporters cli
The -d flag points Newman at the data file. Same iterations, same per-row assertions, same report — but in CI rather than the GUI.
Negative cases earn their keep here
The most underrated use of data-driven testing is negative cases. A loop over twenty bad inputs runs in seconds:
input,description,expectedStatus,empty string,400" ",whitespace only,400"a",too short,400"<script>alert(1)</script>",XSS,400"' OR 1=1 --",SQL injection,400"𝓊𝓃𝒾𝒸𝑜𝒹𝑒",fancy unicode,400"a@b@c",double @ in email,400
Each row has a description field that drives a meaningful test name in the report. Twenty failure cases that would have taken 40 minutes to write as individual tests live in one CSV and run together.
When data-driven hurts
Not every test should be data-driven. Reach for it when:
You have many inputs that exercise the same logic.
The expected behaviour can be expressed as a function of the input row.
Avoid it when:
The cases are fundamentally different flows (login vs registration vs password reset). Use separate requests.
Each case needs deeply different assertions (if expectedStatus === 200, also assert X is fine; whole different bodies of test code is too much).
The data file is two rows long. Two named requests are clearer.
⚠️ Common mistakes
Treating CSV values as their JS types. Numbers and booleans come in as strings. Always parseInt(...) or === "true" before comparing in tests, or use a JSON data file that preserves types.
Forgetting to escape commas inside CSV cells."hello, world" works; hello, world is two columns. When in doubt, use JSON.
Letting the runner blast a real API. A 500-row CSV against a production endpoint without a delay is a small denial-of-service attack on your own service. Set a 100-200ms delay between iterations or run against a sandbox.
🎯 Practice task
Run a data-driven test. 30-40 minutes.
Create a posts-test-data.csv file on your disk with this content:
userId,title,body,expectedStatus1,Valid post,Body for valid post,2012,Another valid,More body,2011,,No body field but title is fine,201,No userId,A body,2011,Special <script>alert(1)</script> title,Body,201
(JSONPlaceholder is lenient — it returns 201 for almost anything. The point is the workflow; for a real API you'd vary expectedStatus more.)
Open POST Create Post in your collection. Set the body to use data variables:
Right-click the collection → Run collection. In the Data section, click Select File and pick your CSV. The iteration count auto-fills to 5. Click Run.
Review the per-iteration report — five rows, five test results, each with a descriptive name from the iteration's title.
Add a JSON variant. Make a posts-test-data.json array with the same content. Re-run with the JSON file. Note the difference: userId comes in as a number directly, no parseInt needed.
Add a delay. In the Runner config, set Delay to 200ms. Re-run. Notice the runner pauses between iterations — useful when the target API rate-limits.
Stretch: export the run report (top-right of the Runner → Export Results) as JSON. Open it. Each iteration's request, response, and assertions are all logged — this is the same shape Newman produces for CI.
You can now run a single Postman request against any number of input rows. The next lesson covers the part of API testing that data-driven runs almost always interact with: authentication — API keys, Bearer tokens, Basic auth, and OAuth 2.0 flows.
// tip to track lessons you complete and pick up where you left off across devices.