close

Test

test defines a test case. It supports chainable modifiers and fixture extension for flexible and powerful test definitions.

Alias: it.

test

  • Type: (name: string, fn: (testContext: TestContext) => void | Promise<void>, timeout?: number) => void

Defines a test case.

import { expect, test } from '@rstest/core';

test('should add two numbers correctly', () => {
  expect(1 + 1).toBe(2);
  expect(1 + 2).toBe(3);
});

test.only

Only run certain tests in a test file.

test.only('run only this test', () => {
  // ...
});

test.skip

Skips certain tests.

test.skip('skip this test', () => {
  // ...
});

test.todo

Marks certain tests as todo.

test.todo('should implement this test');

test.each

  • Type: test.each(cases: ReadonlyArray<T>)(name: string, fn: (param: T) => void | Promise<void>, timeout?: number) => void

Runs the same test logic for each item in the provided array.

test.each([
  { a: 1, b: 2, sum: 3 },
  { a: 2, b: 2, sum: 4 },
])('adds $a + $b', ({ a, b, sum }) => {
  expect(a + b).toBe(sum);
});

You can also use a tagged template literal table syntax for more readable parameterized tests:

test.each`
  a    | b    | expected
  ${1} | ${2} | ${3}
  ${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }) => {
  expect(a + b).toBe(expected);
});

The first row defines the parameter names (column headers), and each subsequent row provides the values via template expressions (${...}). Columns are separated by |.

Since the table values are untyped by default, you can provide an explicit generic type parameter for type safety:

test.each<{ a: number; b: number; expected: number }>`
  a    | b    | expected
  ${1} | ${2} | ${3}
  ${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }) => {
  expect(a + b).toBe(expected);
});

You can inject parameters with printf formatting in the test name in the order of the test function parameters.

  • %s: String
  • %d: Number
  • %i: Integer
  • %f: Floating point value
  • %j: JSON
  • %o: Object
  • %#: 0-based index of the test case
  • %$: 1-based index of the test case
  • %%: Single percent sign ('%')
test.each([
  [1, 2, 3],
  [2, 2, 4],
])('adds %i + %i to equal %i', (a, b, sum) => {
  expect(a + b).toBe(sum);
});

// this will return
// adds 1 + 2 to equal 3
// adds 2 + 2 to equal 4

You can also access object properties and array elements with $ prefix:

test.each([
  { a: 1, b: 1, sum: 2 },
  { a: 1, b: 2, sum: 3 },
  { a: 2, b: 1, sum: 3 },
])('adds $a + $b to equal $sum', ({ a, b, sum }) => {
  expect(a + b).toBe(sum);
});

// this will return
// adds 1 + 1 to equal 2
// adds 1 + 2 to equal 3
// adds 2 + 1 to equal 3

test.each([
  [1, 1, 2],
  [1, 2, 3],
  [2, 1, 3],
])('add $0 + $1 to equal $2', (a, b, sum) => {
  expect(a + b).toBe(sum);
});

// this will return
// add 1 + 1 to equal 2
// add 1 + 2 to equal 3
// add 2 + 1 to equal 3

test.for

  • Type: test.for(cases: ReadonlyArray<T>)(name: string, fn: (param: T, testContext: TestContext) => void | Promise<void>, timeout?: number) => void

Alternative to test.each to provide TestContext.

test.for([
  { a: 1, b: 2 },
  { a: 2, b: 2 },
])('adds $a + $b', ({ a, b }, { expect }) => {
  expect(a + b).matchSnapshot();
});

test.for also supports the tagged template literal table syntax:

test.for`
  a    | b    | expected
  ${1} | ${2} | ${3}
  ${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }, { expect }) => {
  expect(a + b).toBe(expected);
});

You can provide an explicit generic type parameter for type safety:

test.for<{ a: number; b: number; expected: number }>`
  a    | b    | expected
  ${1} | ${2} | ${3}
  ${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }, { expect }) => {
  expect(a + b).toBe(expected);
});

test.fails

Marks the test as expected to fail.

test.fails('should fail', () => {
  throw new Error('This test is expected to fail');
});

test.concurrent

Runs the test concurrently with consecutive concurrent flags.

describe('suite', () => {
  test('serial test', async () => {
    /* ... */
  });
  test.concurrent('concurrent test 1', async () => {
    /* ... */
  });
  test.concurrent('concurrent test 2', async () => {
    /* ... */
  });
  test('serial test 1', async () => {
    /* ... */
  });
});

test.sequential

Runs the test sequentially (default behavior).

describe('suite', () => {
  test('serial test', async () => {
    /* ... */
  });
  test('serial test 1', async () => {
    /* ... */
  });
});

test.runIf

Runs the test only if the condition is true.

test.runIf(process.env.RUN_EXTRA === '1')('conditionally run', () => {
  // ...
});

test.skipIf

Skips the test if the condition is true.

test.skipIf(process.platform === 'win32')('skip on Windows', () => {
  // ...
});

test.extend

  • Type: test.extend(fixtures: Fixtures)

Extends the test context with custom fixtures and returns a new test API. The original test is not modified — you can have multiple independent extended versions at the same time.

Fixtures are reusable context entries that help you prepare test resources once and inject them where needed. Typical uses include:

  • Sharing test data and helper clients (for example, API clients, tokens, test users).
  • Wrapping setup/teardown logic in one place instead of repeating it in every test.
  • Building fixture dependencies (one fixture can consume another fixture).
  • Running global-per-test side effects automatically (for example, logging) via auto fixtures.
import { test } from '@rstest/core';

const myTest = test.extend({
  user: async ({}, use) => {
    await use({ name: 'Alice' });
  },
});

// Use myTest (not test) to define tests that need the fixture
myTest('has user in context', ({ user, expect }) => {
  expect(user.name).toBe('Alice');
});

// The original test is unaffected and cannot access user
test('plain test', ({ expect }) => {
  expect(1).toBe(1);
});

The returned API has the same chainable modifiers as test (only, skip, each, concurrent, etc.) and can call .extend() again for further extension.

Fixture function lifecycle

A fixture function receives two parameters:

  1. context — contains other fixtures as well as TestContext (task, expect, onTestFinished, onTestFailed). Use object destructuring to declare the dependencies you need.
  2. use — call await use(value) to pass the fixture value to the test.

Code before await use(value) is setup; code after it is teardown (runs after the test finishes).

import { test } from '@rstest/core';

const testWithDb = test.extend({
  db: async ({}, use) => {
    // setup: create connection
    const db = await connectTestDb();
    await use(db);
    // teardown: close connection
    await db.close();
  },
});

Plain value fixtures

If a fixture does not need setup/teardown logic, you can provide a plain value directly:

import { test } from '@rstest/core';

const myTest = test.extend({
  baseURL: 'https://api.example.com',
});

myTest('uses baseURL', ({ baseURL, expect }) => {
  expect(baseURL).toBe('https://api.example.com');
});

Accessing TestContext

The first parameter of a fixture function also includes TestContext, so you can read current test information or use expect directly inside a fixture:

import { test } from '@rstest/core';

const myTest = test.extend({
  traceId: async ({ task }, use) => {
    // task.name comes from TestContext
    await use(`trace:${task.name}`);
  },
});

Fixture dependencies

A fixture can destructure other fixtures from its first parameter. Rstest automatically initializes them in dependency order and runs teardown in reverse order:

import { test } from '@rstest/core';

const testWithApi = test.extend({
  baseURL: 'https://api.example.com',
  token: async ({ baseURL }, use) => {
    const token = await createTestToken(baseURL);
    await use(token);
    await revokeTestToken(token);
  },
});

testWithApi('fetch profile', async ({ baseURL, token, expect }) => {
  // baseURL → token initialization order is resolved automatically
  const res = await fetch(`${baseURL}/profile`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  expect(res.ok).toBe(true);
});

Automatic fixtures (auto)

Fixtures are lazy by default: they only run when destructured by the test function (or required by another fixture). To make a fixture run for every test automatically — even when not destructured — use the tuple syntax with { auto: true }:

import { test } from '@rstest/core';

const events: string[] = [];

const myTest = test.extend({
  logger: [
    async ({ task }, use) => {
      events.push(`start:${task.name}`);
      await use(undefined);
      events.push(`end:${task.name}`);
    },
    { auto: true },
  ],
});

myTest('runs logger automatically', ({ expect }) => {
  // logger is not destructured here, but it still runs because of auto: true
  expect(events).toContain('start:runs logger automatically');
});

Type inference and explicit generics

Fixture types are usually inferred automatically. If inference is not precise enough, provide an explicit generic to test.extend:

import { test } from '@rstest/core';

interface MyFixtures {
  user: { name: string; role: 'admin' | 'guest' };
}

const myTest = test.extend<MyFixtures>({
  user: async ({}, use) => {
    await use({ name: 'Alice', role: 'admin' });
  },
});

myTest('typed fixture', ({ user, expect }) => {
  // user is typed as { name: string; role: 'admin' | 'guest' }
  expect(user.role).toBe('admin');
});

Fixture types only take effect on the new API returned by test.extend. The type signature of the original test remains unchanged.

Chainable modifiers

test supports chainable modifiers, so you can use them together. For example:

  • test.only.runIf(condition) (or test.runIf(condition).only) will only run the test block if the condition is true.
  • test.skipIf(condition).concurrent (or test.concurrent.skipIf(condition)) will skip the test block if the condition is true, otherwise run the tests concurrently.
  • test.runIf(condition).concurrent (or test.concurrent.runIf(condition)) will only run the test block concurrently if the condition is true.
  • test.only.concurrent (or test.concurrent.only) will only run the test block concurrently.
  • test.for(cases).concurrent (or test.concurrent.for(cases)) will run the test block concurrently for each case in the provided array.
  • ......

Types

TestContext

TestContext provides some APIs, context information, and custom fixtures related to the current test.

export interface TestContext {
  /**
   * Metadata of the current test
   */
  task: {
    /**
     * A unique identifier for the test.
     * The format is `{fileHash}_{suiteIndex}_{testIndex}_...`, for example `419cefd87e_0_0`.
     */
    id: string;
    /** Test name provided by user */
    name: string;
    /** Result of the current test, undefined if the test is not run yet */
    result?: TestResult;
  };
  /** The `expect` API bound to the current test */
  expect: Expect;
  /** The `onTestFinished` hook bound to the current test */
  onTestFinished: OnTestFinished;
  /** The `onTestFailed` hook bound to the current test */
  onTestFailed: OnTestFailed;
}

You can also extend TestContext with custom fixtures using test.extend.