Parameterised tests for Jest that enable running the same test multiple times with different data sets using arrays or tagged template literals
85
Pending
Does it follow best practices?
Impact
85%
1.10xAverage score across 10 eval scenarios
Pending
The risk profile of this skill
jest-each is a parameterized testing library that enables running the same test multiple times with different data sets. It provides two main approaches: array-based parameterization and tagged template literal syntax with readable table formatting. The library integrates seamlessly with all Jest test functions and modifiers.
npm install jest-eachimport each from "jest-each";For CommonJS:
const each = require("jest-each").default;Named imports:
import each, { bind } from "jest-each";import each from "jest-each";
// Array of arrays
each([
[1, 2, 3],
[4, 5, 9],
[7, 8, 15]
]).test('adds %i + %i to equal %i', (a, b, expected) => {
expect(a + b).toBe(expected);
});
// Array of objects
each([
{ name: 'John', age: 25 },
{ name: 'Jane', age: 30 }
]).test('user $name is $age years old', ({ name, age }) => {
expect(typeof name).toBe('string');
expect(typeof age).toBe('number');
});each`
a | b | expected
${1} | ${2} | ${3}
${4} | ${5} | ${9}
${7} | ${8} | ${15}
`.test('adds $a + $b to equal $expected', ({ a, b, expected }) => {
expect(a + b).toBe(expected);
});Creates parameterized test functions that work with all Jest test methods and modifiers.
/**
* Main function to create parameterized tests using arrays or template literals
* @param table - Test data as array of arrays, array of objects, or template literal
* @param data - Additional template data when using template literals
* @returns Object with Jest test methods (test, it, describe, etc.)
*/
function each(table: EachTable, ...data: TemplateData): {
test: EachTestFunction;
it: EachTestFunction;
describe: EachDescribeFunction;
xit: EachTestFunction;
fit: EachTestFunction;
xtest: EachTestFunction;
fdescribe: EachDescribeFunction;
xdescribe: EachDescribeFunction;
};
type EachTable = ArrayTable | TemplateTable;
type ArrayTable = Table | Row;
type Table = ReadonlyArray<Row>;
type Row = ReadonlyArray<unknown>;
type TemplateTable = TemplateStringsArray;
type TemplateData = ReadonlyArray<unknown>;Creates each function bound to a specific global context for custom Jest environments.
/**
* Creates each function bound to specific global context
* @param g - Global Jest context object
* @returns Function that accepts table and returns test methods
*/
each.withGlobal: (g: Global) => (table: EachTable, ...data: TemplateData) => ReturnType<typeof each>;
interface Global {
test: TestFunction;
it: TestFunction;
describe: DescribeFunction;
xit: TestFunction;
fit: TestFunction;
xtest: TestFunction;
fdescribe: DescribeFunction;
xdescribe: DescribeFunction;
}All returned test functions support Jest's standard interface with modifiers.
interface EachTestFunction {
/**
* Creates a parameterized test
* @param title - Test title with optional placeholders
* @param testFn - Test function receiving parameters from data table
* @param timeout - Optional timeout in milliseconds
*/
(title: string, testFn: EachTestFn<TestFn>, timeout?: number): void;
/** Run only this test */
only: EachTestFunction;
/** Skip this test */
skip: EachTestFunction;
/** Concurrent test execution */
concurrent: EachConcurrentTestFunction;
}
interface EachConcurrentTestFunction {
(title: string, testFn: EachTestFn<ConcurrentTestFn>, timeout?: number): void;
only: EachConcurrentTestFunction;
skip: EachConcurrentTestFunction;
}
interface EachDescribeFunction {
/**
* Creates a parameterized describe block
* @param title - Describe title with optional placeholders
* @param suiteFn - Suite function receiving parameters from data table
* @param timeout - Optional timeout in milliseconds
*/
(title: string, suiteFn: EachTestFn<BlockFn>, timeout?: number): void;
/** Run only this describe block */
only: EachDescribeFunction;
/** Skip this describe block */
skip: EachDescribeFunction;
}
type EachTestFn<T extends TestCallback> = (...args: ReadonlyArray<any>) => ReturnType<T>;Core binding function for creating custom parameterized test functions.
/**
* Creates parameterized test functions by binding to Jest global methods
* @param cb - Global Jest callback function to bind to
* @param supportsDone - Whether the callback supports done callback (default: true)
* @param needsEachError - Whether to pass error context (default: false)
* @returns Function that accepts table data and returns bound test function
*/
function bind<EachCallback extends TestCallback>(
cb: GlobalCallback,
supportsDone?: boolean,
needsEachError?: boolean
): EachTestFn<any>;
type GlobalCallback = (
testName: string,
fn: ConcurrentTestFn,
timeout?: number,
eachError?: Error
) => void;
type TestCallback = BlockFn | TestFn | ConcurrentTestFn;jest-each supports printf-style placeholders for automatic value formatting in test titles:
%s - String formatting%d, %i - Integer formatting%f - Float formatting%j - JSON formatting%o, %O - Object formatting%p - Pretty-print formatting (with depth control)%# - Zero-based index placeholder%$ - One-based index placeholdereach([
[1, 2, 3],
[4, 5, 9]
]).test('test %# adds %i + %i to equal %i', (a, b, expected) => {
expect(a + b).toBe(expected);
});
// Results in: "test 0 adds 1 + 2 to equal 3", "test 1 adds 4 + 5 to equal 9"When using template literals or object arrays, variables can be interpolated using $variable syntax:
each`
name | age | status
${'John'} | ${25} | ${'active'}
${'Jane'} | ${30} | ${'inactive'}
`.test('user $name (age $age) is $status', ({ name, age, status }) => {
expect(typeof name).toBe('string');
});Template variables support deep property access using dot notation:
each([
{ user: { profile: { name: 'John' }, age: 25 } },
{ user: { profile: { name: 'Jane' }, age: 30 } }
]).test('user $user.profile.name is $user.age years old', ({ user }) => {
expect(user).toBeDefined();
});Most basic format for simple parameter lists:
each([
[1, 2, 3], // First test case
[4, 5, 9], // Second test case
[7, 8, 15] // Third test case
]).test('adds %i + %i to equal %i', (a, b, expected) => {
expect(a + b).toBe(expected);
});Provides named parameters for better readability:
each([
{ input: 'hello', expected: 5 },
{ input: 'world', expected: 5 },
{ input: '', expected: 0 }
]).test('$input has length $expected', ({ input, expected }) => {
expect(input.length).toBe(expected);
});For tests with single parameters:
each([1, 2, 3, 4, 5]).test('number %i is defined', (num) => {
expect(num).toBeDefined();
expect(typeof num).toBe('number');
});Tabular format with column headers for maximum readability:
each`
operation | a | b | expected
${'add'} | ${1} | ${2} | ${3}
${'sub'} | ${5} | ${3} | ${2}
${'mul'} | ${3} | ${4} | ${12}
`.test('$operation: $a and $b equals $expected', ({ operation, a, b, expected }) => {
switch (operation) {
case 'add':
expect(a + b).toBe(expected);
break;
case 'sub':
expect(a - b).toBe(expected);
break;
case 'mul':
expect(a * b).toBe(expected);
break;
}
});All Jest test methods are supported:
each(testData).test('test title', testFn); // Standard test
each(testData).test.only('test title', testFn); // Run only this test
each(testData).test.skip('test title', testFn); // Skip this test
each(testData).test.concurrent('test title', testFn); // Concurrent execution
each(testData).it('test title', testFn); // Alias for test
each(testData).xit('test title', testFn); // Skip test (alias)
each(testData).fit('test title', testFn); // Focus test (alias)
each(testData).xtest('test title', testFn); // Skip test (alias)Parameterized describe blocks for grouping related tests:
each([
{ type: 'user', endpoint: '/users' },
{ type: 'post', endpoint: '/posts' }
]).describe('$type API', ({ type, endpoint }) => {
test('should fetch all items', async () => {
const response = await api.get(endpoint);
expect(response.status).toBe(200);
});
test('should create new item', async () => {
const response = await api.post(endpoint, { name: 'Test' });
expect(response.status).toBe(201);
});
});Support for concurrent test execution with proper typing:
each([
{ url: '/api/users/1' },
{ url: '/api/users/2' },
{ url: '/api/users/3' }
]).test.concurrent('fetches data from $url', async ({ url }) => {
const response = await fetch(url);
expect(response.status).toBe(200);
});Automatic detection and support for Jest's done callback:
each([
{ delay: 100 },
{ delay: 200 }
]).test('async test with $delay ms delay', ({ delay }, done) => {
setTimeout(() => {
expect(delay).toBeGreaterThan(0);
done();
}, delay);
});Comprehensive validation with descriptive error messages:
// Invalid: empty array
each([]).test('title', () => {}); // Throws: "Error: .each called with an empty Array of table data."
// Invalid: tagged template with no data
each``.test('title', () => {}); // Throws: "Error: .each called with an empty Tagged Template Literal of table data."
// Invalid: not an array or template literal
each("invalid").test('title', () => {}); // Throws: ".each must be called with an Array or Tagged Template Literal."Validation for template literal format and argument count:
// Invalid: mismatched arguments
each`
a | b
${1} | ${2}
${3} // Missing second argument
`.test('title', () => {}); // Throws error with missing argument details// Core types for data tables
type Col = unknown;
type Row = ReadonlyArray<Col>;
type Table = ReadonlyArray<Row>;
type ArrayTable = Table | Row;
type TemplateTable = TemplateStringsArray;
type TemplateData = ReadonlyArray<unknown>;
type EachTable = ArrayTable | TemplateTable;
// Test function types
type ValidTestReturnValues = void | undefined;
type TestReturnValue = ValidTestReturnValues | Promise<unknown>;
type DoneFn = (reason?: string | Error) => void;
type TestFn = PromiseReturningTestFn | GeneratorReturningTestFn | DoneTakingTestFn;
type PromiseReturningTestFn = (this: TestContext) => TestReturnValue;
type DoneTakingTestFn = (this: TestContext, done: DoneFn) => ValidTestReturnValues;
type ConcurrentTestFn = () => Promise<unknown>;
type BlockFn = () => void;
// Generic test function type for parameterized tests
type EachTestFn<EachCallback extends TestCallback> = (
...args: ReadonlyArray<any>
) => ReturnType<EachCallback>;
// Template and interpolation types
type Template = Record<string, unknown>;
type Templates = Array<Template>;
type Headings = Array<string>;
// Internal test case representation
type EachTests = ReadonlyArray<{
title: string;
arguments: ReadonlyArray<unknown>;
}>;