Data-Driven API Testing with CSV and JSON
Data-driven testing separates what you test (one parameterized request plus assertions) from what you test it with (a table of inputs and expected results). Instead of 25 near-identical requests, you keep one request and a 25-row CSV. The payoff: coverage you can read at a glance, and adding a test case becomes a one-line edit that any teammate — or a bug report — can contribute.
The core pattern
Take a signup endpoint. One request:
POST {{baseUrl}}/signup
{ "email": "{{email}}", "password": "{{password}}" }
One data file, where each column becomes a variable and each row is an iteration:
email,password,expectedStatus,case
ada@example.com,S3cure!pass,201,valid signup
ada@example.com,short,422,password too short
not-an-email,S3cure!pass,422,invalid email format
,S3cure!pass,422,missing email
ada@example.com,,422,missing password
"admin'--@x.com",S3cure!pass,422,SQL injection probe
And one assertion script that reads the expectation from the row:
rr.test(rr.variables.get('case'), () =>
rr.expect(rr.response.status).toBe(Number(rr.variables.get('expectedStatus'))));
Six iterations, six labelled pass/fail results. Notice the trick: the expected result lives in the data file too, so one script serves happy paths and error paths alike.
What belongs in a dataset
- Boundary values — 0, 1, maximum allowed, maximum + 1, empty string, 255-char string.
- Format edge cases — Unicode names, emoji, leading/trailing spaces,
+in emails, IDs with leading zeros. - Invalid input — wrong types, missing fields, and a row of injection probes.
- Realistic bulk data — 100 product rows to smoke-test a catalog import, exported from a spreadsheet the business team maintains.
Chaining requests inside an iteration
Iterations aren't limited to a single request — the whole selected sequence runs once per row. A classic flow: create order → pay order → check status, driven by a file of order variants. Pass values between steps with runtime variables (rr.variables.set('orderId', rr.response.body.id)); they reset between iterations, so rows can't contaminate each other.
Capture results as data, too
The output of a data-driven run is also worth keeping as a table: iteration, input, status, extracted values. RestRuno's runner can generate a CSV from response expressions like body.id, status or timeMs per iteration — useful as a QA evidence artifact, or as the input file for the next stage of testing (create 50 users today, exercise those 50 users tomorrow).
Practical rules
- Keep data files next to the collection, in Git. A reviewer should see the new edge case in the diff. (See version-controlling collections.)
- One dataset per intent.
signup-validation.csvandsignup-load-smoke.csvage better than one giant file. - Name every row (a
casecolumn) — "iteration 17 failed" is useless; "password too short failed" is a bug report. - Don't hide logic in data. If a row needs its own special assertions, it deserves its own request.
Run it with RestRuno Pro
RestRuno's data-driven runner takes CSV, JSON or TXT files, shows per-iteration results with the full request and response, and can write a results CSV from expressions over each response.
See RestRuno Pro — $20 one-time