Why Parameterize Your Tests
Parameterized testing lets you run the same assertion logic against many different inputs by describing test cases as a data table instead of writing a separate test() block for each case. Jest's test.each() takes an array of arguments (or a tagged template literal) and generates one test per row, so adding a new edge case means adding a row, not duplicating fifteen lines of setup and assertions.
Cricket analogy: Much like a batting coach running Virat Kohli through the same cover-drive drill against a left-arm spinner, a leg-spinner, and a fast bowler in one net session, test.each() runs identical assertion logic across a table of inputs instead of scheduling a separate drill per bowling type.
Table Syntax and Tagged Templates
test.each() accepts two input styles: an array of arrays where each inner array is the argument list for one test run, or a tagged template literal using a markdown-like pipe table that Jest parses into named placeholders such as %s, %i, %d, %p, or %#. The array form is best for simple positional data, while the tagged template form is more readable when you have many named columns, since each column header becomes a variable available directly in the test title and callback via destructuring.
Cricket analogy: Similar to choosing between a plain scorecard listing runs and balls positionally versus a full stats table with named columns for strike rate and boundaries like ESPNcricinfo's ball-by-ball table, the array form of test.each() suits quick positional data while the tagged template suits readable named columns.
// Array form: each row is [input, input, expected]
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 2, 4],
])('add(%i, %i) returns %i', (a, b, expected) => {
expect(add(a, b)).toBe(expected);
});
// Tagged template form: pipe table with named columns
describe.each`
role | canEdit | canDelete
${'admin'} | ${true} | ${true}
${'editor'} | ${true} | ${false}
${'viewer'} | ${false} | ${false}
`('permissions for $role', ({ role, canEdit, canDelete }) => {
test(`canEdit is ${canEdit}`, () => {
expect(getPermissions(role).canEdit).toBe(canEdit);
});
test(`canDelete is ${canDelete}`, () => {
expect(getPermissions(role).canDelete).toBe(canDelete);
});
});describe.each() for Grouped Scenarios
describe.each() works like test.each() but wraps a whole describe block per row instead of a single test, which is useful when each parameterized case needs multiple related assertions, shared beforeEach setup, or nested test blocks. This avoids re-deriving the same input inside every individual test and keeps related expectations grouped under one clearly named describe block per scenario.
Cricket analogy: Similar to running an entire net session, warm-up, throwdowns, and video review, for each opposition team rather than just one drill, describe.each() groups multiple related tests under one scenario block per row, the way a coach preps a full session per opponent like Australia or England.
Column headers in a tagged-template table are available inside the test title using $variableName interpolation and inside the callback via object destructuring, so ${'admin'} in the header row becomes both the string "admin" in the test name and the role property on the destructured argument.
Common Pitfalls
A frequent mistake is passing a single flat array to test.each() when multiple positional arguments are needed, which causes Jest to spread the array's elements as separate arguments instead of passing one array argument, so wrapping single-argument rows in their own array, like [[valueA], [valueB]], is required. Another pitfall is confusing the format-specifier rules of the array form with the different title-interpolation rules of the tagged-template form, since these two APIs build test titles from row data differently.
Cricket analogy: Similar to a scorer misreading a single combined column of runs-and-balls as two separate stats and miscounting the strike rate, forgetting to wrap a single test.each() argument in its own array causes Jest to misinterpret the row shape and spread values incorrectly.
Passing test.each(['a', 'b', 'c']) with a single test callback parameter runs three separate tests with one string each, but test.each([['a', 'b', 'c']]) with three callback parameters runs one test where the string is spread into three single-character arguments unless it is explicitly nested as [['a', 'b', 'c']] to represent one row of three values.
- test.each() runs the same test body once per row of a data table, avoiding duplicated test() blocks for similar cases.
- Rows can be defined as an array of arrays or as a tagged-template pipe table with named columns.
- Format specifiers like %s, %i, %d, %p, and %# interpolate row values into the generated test title.
- describe.each() wraps an entire describe block per row, useful when a scenario needs multiple related assertions or shared setup.
- Tagged-template column headers become both $variable interpolations in titles and destructured properties in the callback.
- A single-argument row must still be wrapped in its own array to avoid Jest spreading it incorrectly.
- Parameterized tests scale test coverage linearly with data rows instead of linearly with duplicated code.
Practice what you learned
1. What does test.each([[1,2,3],[4,5,9]]) generate when the callback is (a, b, expected) => {...}?
2. In a tagged-template test.each table, how do you reference a column named 'role' inside the test title string?
3. Why must test.each(['a','b']) be written as test.each([['a'],['b']]) when the callback takes one parameter per row?
4. What is the main advantage of describe.each() over test.each() for a given scenario?
5. Which format specifier in test.each()'s title string pretty-prints a value using Jest's own pretty-format serializer?
Was this page helpful?
You May Also Like
Testing Node.js APIs and Express Routes
Learn how to test Express routes and Node.js API handlers with Jest and supertest, including mocking databases, external calls, and error-handling middleware.
Jest Configuration: Multi-Project Setups
Learn how to configure Jest's projects array to run multiple test environments and configurations, like unit and integration suites, from a single Jest invocation.
Writing Custom Matchers
Learn how to extend Jest's expect with expect.extend() to write custom matchers that produce clearer, more expressive assertions and failure messages.