Data-driven testing
When ten tests differ only in their input, write one request and a table of inputs. The runner sends the request once per row. Adding a case becomes adding a line, and the table itself is a readable record of what was tested.
When to use it
- The same endpoint with many valid inputs: products, currencies, languages.
- Boundary values around a limit: 0, 1, 10, 11.
- A list of invalid inputs that should all be refused.
- The same journey for several users or roles.
Not for cases that need different steps. Those are separate requests.
The data file
A CSV file has a header row of variable names, then one row per case:
productId,quantity,expectedStatus
1,1,201
2,3,201
3,10,201
1,11,422
2,50,422A JSON file is an array of objects with the same names:
[
{ "productId": 1, "quantity": 1, "expectedStatus": 201 },
{ "productId": 1, "quantity": 11, "expectedStatus": 422 }
]Each column becomes a variable for that pass. In the request, use it like any other variable.
Build one
Using POST /orders and shop-orders.csv from the Shop API sample.
Write the request body with variables where the data goes:
json{ "productId": {{productId}}, "quantity": {{quantity}} }Open the runner for the request, folder or collection.
Select Load data file and choose the CSV. The runner shows a preview of the rows.
Select Run. There is one pass per row.
A value from the file wins over a collection or environment variable of the same name for that pass. See Data-driven runs for the exact rules on files, quoting and types.
Expected results per row
A no-code test has one fixed expected value: Status equals 201 cannot become 422 for row four. Two ways to handle rows that expect different results:
Split the file. One file of inputs that should succeed, run against a request that tests for 201. A second file of inputs that should fail, run against a copy that tests for 422. No code, and each file states its purpose.
Put the expectation in the file and read it in a post-response script:
const expected = Number(pm.iterationData.get('expectedStatus'));
pm.test('status is ' + expected, function () {
pm.response.to.have.status(expected);
});pm.iterationData.get('name') returns the current row's value. See Scripts.
Choosing the rows
A good table is small and deliberate. For each input:
| Pick | For a quantity of 1 to 10 |
|---|---|
| A typical value | 3 |
| The lowest valid value | 1 |
| The highest valid value | 10 |
| Just below the range | 0 |
| Just above the range | 11 |
| Far outside | 50, -1 |
| The wrong kind of thing | abc, empty |
Seven rows cover what seven hundred random ones would not. Add a note column saying why each row exists; the runner ignores columns the request does not use.
quantity,expectedStatus,note
1,201,lowest valid
10,201,highest valid
0,422,below range
11,422,just above rangeGenerated data
When the exact value does not matter, only that it is fresh, use a dynamic variable instead of a file: {{$randomEmail}}, {{$guid}}, {{$randomInt}}. Each send gets a new value, which avoids "that email is already registered" on the second run.
Use a file when you need to know the input in order to judge the output. Use dynamic variables when you only need uniqueness.
Data-driven workflows
Run a whole folder with a data file, and each pass runs the full journey with that row's values: sign in as this user, order this product, check the result. Captured values chain within a pass and do not leak into the next row.
From the command line
The same file works in CI:
apiworkbench-cli run shop-api.apiworkbench.json --folder Orders -d shop-orders.csvSee Automating in CI.
Common mistakes
- A header name that does not match the variable.
Quantityandquantityare different. - Quotes inside JSON bodies. A text value needs its quotes in the body:
"name": "{{name}}". A number does not. - Hundreds of rows. If a row fails, can you say what it was testing? If not, it should not be there.
- Secrets in data files. A CSV of real credentials is a leak waiting for a commit.