Parameterised tests for Jest that enable running the same test multiple times with different data sets using arrays or tagged template literals
npx @tessl/cli install tessl/npm-jest-each@30.1.0jest-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>;
}>;