You wrote a login test. It passes. Then a teammate asks whether it also passes for a locked account, an unverified email, a password with a trailing space, or an SQL-injection string pasted into the field. You can copy the test five times, or you can run the same test against a table of inputs and expected outputs.
Copy-pasted tests are how suites rot. Five near-identical tests drift apart: one gets a new assertion, another misses a field rename, and soon you maintain five tests that should have been one. Parameterized testing fixes that by separating test logic from test data. You write the scenario once, then feed it many cases from CSV or JSON.
What parameterized testing means
Parameterized testing, also called data-driven testing, separates:
- Test logic: send a request, check the status code, validate the response body.
- Test data: inputs and expected outputs for each case.
For example, a discount-code endpoint always follows the same flow:
- Send
POST /api/orders. - Include a discount code and order total.
- Assert the status code.
- Assert the discount value.
Only the data changes:
| code | order_total | expected_status | expected_discount |
|---|---|---|---|
| WELCOME10 | 100 | 200 | 10 |
| WELCOME10 | 5 | 422 | 0 |
| EXPIRED | 100 | 410 | 0 |
| (empty) | 100 | 400 | 0 |
| FAKE123 | 100 | 404 | 0 |
That is five behaviors, but still one test. The runner iterates through each row, binds the row values to variables, sends the request, and checks the assertions.
When row 3 fails because EXPIRED returned 200 instead of 410, the report points to that iteration. You do not need to hunt through duplicated test files.
This pattern is most useful for edge cases:
- Empty strings
- Negative numbers
- Unicode input
- Expired tokens
- Boundary values
- Invalid IDs
- Malformed payloads
- Security-related strings
Adding one more case becomes as cheap as adding one more row.
Why external data files are better than hardcoded cases
You can hardcode cases inside a test, but that breaks down quickly.
External files give you a cleaner workflow:
- QA can add cases without editing test logic.
- Large case sets stay readable.
- Generated test data can be stored as files.
- Test logic stays small.
- Expected results can vary per row.
- Data changes are versioned with your repo.
Use the format that matches your data:
- CSV for flat, tabular cases.
- JSON for nested payloads or structured request bodies.
Apidog supports both CSV and JSON as iteration data, so the choice depends on the data shape.
Create a CSV data file
For tabular cases, start with CSV. The header row defines variable names. Each row is one test iteration.
Create discount-cases.csv:
code,order_total,expected_status,expected_discount
WELCOME10,100,200,10
WELCOME10,5,422,0
EXPIRED,100,410,0
,100,400,0
FAKE123,100,404,0
Each column becomes a variable in the scenario:
{{code}}{{order_total}}{{expected_status}}{{expected_discount}}
Use the input variables in the request body and the expected variables in assertions.
Example request body:
{
"code": "{{code}}",
"order_total": {{order_total}}
}
Example assertions:
- Response status equals
{{expected_status}} - JSON field
discountequals{{expected_discount}}
Use JSON for nested request data
If your payload contains nested objects or arrays, JSON is usually easier than CSV.
Create user-cases.json:
[
{
"scenario": "valid full profile",
"user": {
"email": "ada@example.com",
"roles": ["admin", "billing"],
"profile": {
"country": "US",
"timezone": "America/New_York"
}
},
"expected_status": 201
},
{
"scenario": "missing email",
"user": {
"email": "",
"roles": ["viewer"],
"profile": {
"country": "GB",
"timezone": "Europe/London"
}
},
"expected_status": 400
},
{
"scenario": "unknown role",
"user": {
"email": "grace@example.com",
"roles": ["wizard"],
"profile": {
"country": "CA",
"timezone": "America/Toronto"
}
},
"expected_status": 422
}
]
In the scenario, reference the object with variables such as:
{
"user": {{user}}
}
Use {{expected_status}} in your status assertion.
The scenario field is useful as a label. In reports, a failed iteration can read unknown role instead of just iteration 3.
Keep data files maintainable
Use these rules when your test data grows:
- Keep one concern per file.
- Good:
discount-cases.csv,user-creation-cases.json - Bad: one file with unrelated endpoint cases
- Good:
- Put expected results in the data file.
- Different rows should be able to expect different statuses and response values.
- Quote CSV values that contain commas.
- If many values need escaping, switch to JSON.
- Store data files in your repo.
- Version them with the API and test configuration.
- Do not store secrets in test data.
- Use environment variables or CI secrets instead.
Build the parameterized scenario in Apidog
In Apidog, create the test scenario once.
- Add the request for your endpoint.
- Replace literal request values with variables:
{{code}}{{order_total}}
- Add assertions that use expected values from the data file:
- Status code equals
{{expected_status}} - JSON response field equals
{{expected_discount}}
- Status code equals
- Open the scenario run settings.
- Attach the CSV or JSON file as iteration data.
- Run the scenario.
Apidog reads the file and runs the scenario once per row. Each run binds that row’s values to the matching variables.
If you need assertion examples, see API assertions: a practical guide and how to set assertions and extract variables from a JSON response.
A parameterized scenario is still a normal scenario, so you can group it into a test suite and run it with the rest of your API tests.
Run parameterized tests from the command line
Use the Apidog app to build and debug the scenario. Use the Apidog CLI to run it in CI.
Install the CLI:
npm install -g apidog-cli
The binary is apidog. A basic run points to a scenario ID and environment ID:
apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 -e 1629989 -r cli
To run the scenario with a CSV or JSON data file, add -d:
apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 -e 1629989 \
-d ./discount-cases.csv -r cli,junit --out-dir ./test-reports
The -d flag, also available as --iteration-data, is what turns a single scenario run into a data-driven run.
You can also pass a JSON file:
apidog run --access-token $APIDOG_ACCESS_TOKEN -t 605067 -e 1629989 \
-d ./user-cases.json -r cli,junit --out-dir ./test-reports
Keep the access token in your CI secret store. Do not commit it to your repo.
Useful CLI flags:
| Flag | Purpose |
|---|---|
-d, --iteration-data <path> |
Use a CSV or JSON file as iteration data |
-n, --iteration-count <n> |
Run a scenario a fixed number of times |
-r, --reporters <list> |
Choose output formats such as cli and junit
|
--out-dir <path> |
Write reports to a directory |
To check the current options for your installed version, run:
apidog run --help
Run parameterized API tests in GitHub Actions
Here is a GitHub Actions workflow that installs the CLI and runs a CSV-driven scenario on every pull request:
name: API tests
on: [pull_request]
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Apidog CLI
run: npm install -g apidog-cli
- name: Run parameterized API tests
env:
APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}
run: |
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./tests/discount-cases.csv \
-r cli,junit --out-dir ./test-reports
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: api-test-report
path: ./test-reports
This workflow does four things:
- Checks out the repo.
- Installs Node and the Apidog CLI.
- Runs the parameterized scenario with
-d. - Uploads reports even when the test fails.
The junit reporter writes XML that CI dashboards can parse into individual test results. That makes failed iterations easier to inspect than raw logs.
For more detail, see how to automate API tests in GitHub Actions.
The same pattern works in GitLab CI, Jenkins, CircleCI, and other CI systems:
- Install Node.
- Install
apidog-cli. - Expose
APIDOG_ACCESS_TOKENas an environment variable. - Run
apidog runwith-d.
Compare parameterized testing approaches
Apidog is one option for data-driven API tests, but it is useful to know the tradeoffs.
Postman and Newman
Postman’s Collection Runner and Newman both support CSV and JSON data files. You attach a data file, reference {{column}} variables, and run the collection.
This works well, but more complex assertions usually live in JavaScript pre-request and test scripts. As the suite grows, you maintain more script code.
If you are comparing command-line runners, see Postman CLI vs Newman.
Code-first test frameworks
Frameworks like these give you full programming-language control:
-
pytestwith@pytest.mark.parametrize - JUnit with
@ParameterizedTest - REST Assured
They are a good fit when tests need custom setup, generated data, complex branching, or tight integration with an existing codebase.
The tradeoff is that cases usually live in code, and the team maintains the HTTP plumbing.
Apidog
Apidog keeps the scenario visual and the data external. That makes it easier for QA and developers to add cases while still running the same scenario headlessly in CI.
If your main requirement is CSV or JSON data-driven API testing, see which tool for data-driven API testing with CSV or JSON.
A practical workflow that scales
Use this workflow to introduce parameterized testing without rewriting your suite.
1. Start with one endpoint
Pick an endpoint that has caused bugs before.
Create one Apidog scenario with:
- Variables in the request body
- Assertions based on expected values
- A small CSV or JSON file
Start with three rows:
code,order_total,expected_status,expected_discount
WELCOME10,100,200,10
WELCOME10,5,422,0
EXPIRED,100,410,0
Run it in the app until all iterations behave as expected.
2. Add rows when bugs appear
When a bug report comes in, add the failing input as a new row with the correct expected output.
The bug becomes a permanent regression test without adding a new test file.
3. Move the run to CI
Add the CLI command to your pipeline:
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./tests/discount-cases.csv \
-r cli,junit --out-dir ./test-reports
Now every pull request runs the full table.
4. Maintain logic once
When the response shape changes, update the assertion once. Every row uses the updated logic.
When you need more coverage, add rows. The test stays small while coverage expands.
Frequently asked questions
What is the difference between parameterized testing and data-driven testing?
They usually mean the same thing: running one test repeatedly with different inputs and expected outputs supplied from outside the test.
“Parameterized” emphasizes variable binding. “Data-driven” emphasizes the external data source.
Should I use CSV or JSON?
Use CSV when every case has the same flat columns.
Use JSON when the request payload contains nested objects, arrays, or structured data.
Apidog supports both formats as iteration data.
Will hundreds of iterations slow down CI?
Each row runs the scenario once, so runtime scales with row count and API latency.
If a large file slows your pipeline, split the data:
- Run a small smoke set on every pull request.
- Run the full set nightly or before release.
How do I keep secrets out of data files?
Do not put tokens, passwords, or credentials in CSV or JSON test data.
Use environment variables or CI secrets, for example:
$APIDOG_ACCESS_TOKEN
Anyone with repo access can read test data files, so treat them as non-secret.
Can I run the same scenario against staging and production?
Yes. Keep the scenario and data file the same, then switch environments with -e.
Example:
# Staging
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 1629989 \
-d ./tests/discount-cases.csv -r cli
# Production
apidog run --access-token $APIDOG_ACCESS_TOKEN \
-t 605067 -e 9999999 \
-d ./tests/discount-cases.csv -r cli
This works because environment configuration and iteration data are separate inputs.
Wrapping up
Parameterized API testing turns duplicated tests into reusable scenarios plus data files. Write the test once, put each case in CSV or JSON, and let the runner execute every row.
Apidog provides the visual scenario builder, CSV and JSON iteration data, and the apidog run -d command for CI execution. Start with one endpoint, add real-world cases as rows, and run the table automatically on every change.
Top comments (0)