Test
test 用于定义一个测试用例,支持链式调用和 fixture 扩展。
别名:it。
test
- 类型:
(name: string, fn: (testContext: TestContext) => void | Promise<void>, timeout?: number) => void
定义一个测试用例。
import { expect, test } from '@rstest/core';
test('should add two numbers correctly', () => {
expect(1 + 1).toBe(2);
expect(1 + 2).toBe(3);
});
test.only
只运行测试文件中的某些测试。
test.only('run only this test', () => {
// ...
});
test.skip
跳过某些测试。
test.skip('skip this test', () => {
// ...
});
test.todo
将某些测试标记为待办。
test.todo('should implement this test');
test.each
- 类型:
test.each(cases: ReadonlyArray<T>)(name: string, fn: (param: T) => void | Promise<void>, timeout?: number) => void
对提供的数组中的每一项运行相同的测试逻辑。
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);
});
你也可以使用标签模板字面量的表格语法,使参数化测试更具可读性:
test.each`
a | b | expected
${1} | ${2} | ${3}
${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }) => {
expect(a + b).toBe(expected);
});
第一行定义参数名(列标题),后续每行通过模板表达式(${...})提供值,列之间用 | 分隔。
由于表格中的值默认是无类型的,你可以通过显式泛型参数来获得类型支持:
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);
});
你可以使用 printf formatting 来格式化测试名称中的参数。
%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);
});
// 此时将返回:
// adds 1 + 2 to equal 3
// adds 2 + 2 to equal 4
你也可以使用 $ 前缀访问对象属性和数组元素:
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);
});
// 此时将返回:
// 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);
});
// 此时将返回:
// add 1 + 1 to equal 2
// add 1 + 2 to equal 3
// add 2 + 1 to equal 3
test.for
- 类型:
test.for(cases: ReadonlyArray<T>)(name: string, fn: (param: T, testContext: TestContext) => void | Promise<void>, timeout?: number) => void
test.each 的替代方案,提供 TestContext。
test.for([
{ a: 1, b: 2 },
{ a: 2, b: 2 },
])('adds $a + $b', ({ a, b }, { expect }) => {
expect(a + b).matchSnapshot();
});
test.for 同样支持标签模板字面量的表格语法:
test.for`
a | b | expected
${1} | ${2} | ${3}
${2} | ${3} | ${5}
`('$a + $b = $expected', ({ a, b, expected }, { expect }) => {
expect(a + b).toBe(expected);
});
你可以通过显式泛型参数来获得类型支持:
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
标记该测试预期会失败。
test.fails('should fail', () => {
throw new Error('This test is expected to fail');
});
test.concurrent
并发运行连续带有 concurrent 标记的测试。
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
顺序(串行)运行测试(默认行为)。
describe('suite', () => {
test('serial test', async () => {
/* ... */
});
test('serial test 1', async () => {
/* ... */
});
});
test.runIf
仅当条件为真时才运行该测试。
test.runIf(process.env.RUN_EXTRA === '1')('conditionally run', () => {
// ...
});
test.skipIf
当条件为真时跳过该测试。
test.skipIf(process.platform === 'win32')('skip on Windows', () => {
// ...
});
test.extend
- 类型:
test.extend(fixtures: Fixtures)
通过自定义 fixture 扩展测试上下文,返回一个新的 test API。原始 test 不会被修改,你可以同时拥有多个各自独立的扩展版本。
Fixture 是可复用的上下文条目,用来准备测试资源并按需注入到测试里。常见用途包括:
- 共享测试数据和辅助客户端(例如 API client、token、测试用户)。
- 把 setup/teardown 逻辑集中在一处,避免每个测试重复写。
- 构建 fixture 依赖(一个 fixture 可以依赖另一个 fixture)。
- 通过
auto fixture 自动执行每个测试都需要的副作用(例如日志记录)。
import { test } from '@rstest/core';
const myTest = test.extend({
user: async ({}, use) => {
await use({ name: 'Alice' });
},
});
// 使用 myTest(而非 test)来定义需要 fixture 的测试
myTest('has user in context', ({ user, expect }) => {
expect(user.name).toBe('Alice');
});
// 原始 test 不受影响,也无法访问 user
test('plain test', ({ expect }) => {
expect(1).toBe(1);
});
返回的 API 拥有与 test 相同的链式修饰符(only、skip、each、concurrent 等),并且可以继续调用 .extend() 进行二次扩展。
Fixture 函数的生命周期
Fixture 函数接收两个参数:
- context — 包含其他 fixture 以及
TestContext(task、expect、onTestFinished、onTestFailed)。使用对象解构来声明你需要的依赖。
- use — 调用
await use(value) 把 fixture 的值传递给测试。
await use(value) 之前的代码是 setup,之后的代码是 teardown(测试结束后执行)。
import { test } from '@rstest/core';
const testWithDb = test.extend({
db: async ({}, use) => {
// setup:创建连接
const db = await connectTestDb();
await use(db);
// teardown:关闭连接
await db.close();
},
});
普通值 fixture
如果 fixture 不需要 setup/teardown 逻辑,可以直接提供一个普通值:
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');
});
访问 TestContext
Fixture 函数的第一个参数中同时包含 TestContext,因此你可以在 fixture 内直接读取当前测试信息或使用 expect:
import { test } from '@rstest/core';
const myTest = test.extend({
traceId: async ({ task }, use) => {
// task.name 来自 TestContext
await use(`trace:${task.name}`);
},
});
Fixture 之间的依赖
一个 fixture 可以在第一个参数中解构其他 fixture,Rstest 会自动按依赖顺序初始化它们,并按反序执行 teardown:
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 的初始化顺序由依赖关系自动决定
const res = await fetch(`${baseURL}/profile`, {
headers: { Authorization: `Bearer ${token}` },
});
expect(res.ok).toBe(true);
});
自动 fixture(auto)
Fixture 默认是按需执行的:只有测试函数中解构了它(或被其他 fixture 依赖)才会运行。如果你希望某个 fixture 对每个测试都自动生效——即使测试没有解构它——可以使用元组语法并设置 { 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,但因为 auto: true,fixture 仍会执行
expect(events).toContain('start:runs logger automatically');
});
类型推断与显式泛型
Fixture 的类型通常可以自动推断。如果推断不够精确,可以为 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 的类型为 { name: string; role: 'admin' | 'guest' }
expect(user.role).toBe('admin');
});
注意:fixture 类型只在 test.extend 返回的新 API 上生效。原始 test 的类型签名不会改变。
Types
TestContext
TestContext 提供一些和当前测试有关的 API、上下文信息,以及自定义的 fixture。
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;
}
你也可以通过 test.extend 方法通过自定义 fixture 的方式来扩展测试上下文。