Data-Driven API Testing
Separate the test logic from the test data, so adding a new case is a one-line data change instead of writing a whole new test.
What you'll learn
- Refactor several near-duplicate test cases into one data-driven test
- Explain the maintenance benefit of separating test data from test logic
- Design a data table covering several of this course's earlier test-design techniques at once
Prerequisites
Explanation
Five near-identical test functions, each hard-coding one input and one expected output, are five places to update every time the validation rule changes even slightly — and five places a future edit can accidentally update four of and forget the fifth. Data-driven testing separates the data (which specific inputs and expected outputs) from the logic (how to run the check), so the logic is written exactly once and the data lives in one clearly readable table.
The shape is simple: a list of cases, each with an input and an expected result, run through a single loop that applies the same check to every row. Adding a new test case becomes adding one row to the table — no new function, no copy-pasted boilerplate, no risk of the copy silently diverging from the original.
This connects directly to the test design techniques from earlier in this course and Software Testing Foundations: equivalence partitioning and boundary-value analysis are exactly how you decide which rows belong in the data table in the first place. A data-driven test for an age-validation endpoint might have rows for: a negative age (invalid), age 0 (boundary), age 17 (equivalence class: under minimum), age 18 (boundary), age 65 (comfortably valid), age 150 (equivalence class: implausibly high). The technique from earlier lessons decides the content; data-driven testing decides the structure that makes maintaining many such cases sustainable.
The maintenance payoff compounds as an API grows: a validation rule that starts with 3 test cases and grows to 30 over a project's life is either 30 separate near-duplicate functions (each one a small maintenance liability) or one function and a 30-row table (one place to fix a bug in the check itself, however many rows the table eventually holds).
Example
Five separate near-duplicate test functions collapsed into one data-driven check — same coverage, one place to fix the logic.
function isValidAge(age) {
return Number.isInteger(age) && age >= 18 && age <= 120;
}
const cases = [
{ input: -1, expected: false },
{ input: 0, expected: false },
{ input: 17, expected: false },
{ input: 18, expected: true },
{ input: 65, expected: true },
{ input: 150, expected: false },
];
const results = cases.map((c) => ({ ...c, actual: isValidAge(c.input), pass: isValidAge(c.input) === c.expected }));
console.log(results.every((r) => r.pass)); // true -- every row passesTry it yourself
Add one more row to the cases array for age 120 (the upper boundary) and re-run.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Using isValidAge and cases already defined, count how many rows fail by setting failCount.
Checks: correctly counts the one failing row
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Write a function runDataDrivenTest(fn, cases) that takes a function and an array of { input, expected } rows, and returns an array of the input values that FAILED (where fn(input) !== expected).
Checks: correctly identifies the one failing input · returns an empty array when all rows pass
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Copy-pasting a test function for each new case instead of adding a row to a shared data table, multiplying the places a future bug fix needs to be applied.
- Building a data table with only "obviously valid" rows, skipping the equivalence-class and boundary-value thinking that decides which rows actually matter.
- Making the data-driven runner itself so complex that it becomes harder to trust than the simple duplicated functions it replaced.
Knowledge check
Takeaway
Data-driven testing separates test data from test logic, turning "add a new test case" into "add one row to a table" instead of writing and maintaining a new near-duplicate function.
Summary
This lesson covered collapsing near-duplicate test cases into one data-driven test, and how earlier test-design techniques decide which rows the data table should contain.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.