测试 API 参考
以下类型定义在后续类型签名中使用:
type Awaitable<T> = T | PromiseLike<T>;
type TestFunction = () => Awaitable<void>;
interface TestOptions {
/**
* 如果执行时间超过此值,测试将失败。
*/
timeout?: number;
/**
* 测试失败时,将重试指定次数。
*
* @default 0
*/
retry?: number;
/**
* 即使每次都失败,也会多次重复运行相同的测试。
* 如果设置了 "retry" 选项且测试失败,它将在每个循环中用尽所有重试次数。
* 这对于调试偶发性失败(flaky tests)非常有用。
*
* @default 0
*/
repeats?: number;
}当测试函数返回一个 Promise 时,测试运行器将等待 Promise 解析以收集异步断言。如果 Promise 被拒绝,测试将失败。
TIP
在 Jest 中,TestFunction 也可以是 (done: DoneCallback) => void 类型。如果使用这种形式,测试将不会在 done 被调用之前结束。你可以使用 async 函数实现同样的效果,请参阅迁移指南中的 Done Callback 部分。
你可以通过在函数上链式调用属性来定义选项:
import { test } from 'vitest';
test.skip('skipped test', () => {
// 某些当前会失败的逻辑
});
test.concurrent.skip('skipped concurrent test', () => {
// 某些当前会失败的逻辑
});你也可以将一个对象作为第二个参数提供:
import { test } from 'vitest';
test('skipped test', { skip: true }, () => {
// 某些当前会失败的逻辑
});
test('skipped concurrent test', { skip: true, concurrent: true }, () => {
// 某些当前会失败的逻辑
});这两种方式功能完全相同,选择哪种纯粹是个人风格偏好。
请注意,如果将超时时间作为最后一个参数提供,则不能再使用选项对象:
import { test } from 'vitest';
// ✅ 这样可以
test.skip('heavy test', () => {
// ...
}, 10_000);
// ❌ 这样不行
test(
'heavy test',
{ skip: true },
() => {
// ...
},
10_000
);但是,你可以在选项对象内部提供超时时间:
import { test } from 'vitest';
// ✅ 这样可以
test('heavy test', { skip: true, timeout: 10_000 }, () => {
// ...
});test
- 别名:
it
test 定义了一组相关的断言。它接收测试名称和一个包含待测试断言的函数。
你可以选择提供一个超时时间(毫秒),用于指定在终止前等待的时长。默认值为 5 秒,可以通过 testTimeout 进行全局配置。
import { expect, test } from 'vitest';
test('should work as expected', () => {
expect(Math.sqrt(4)).toBe(2);
});test.extend
- 别名:
it.extend
使用 test.extend 扩展测试上下文,添加自定义 fixture。这将返回一个新的 test 函数,并且它也是可扩展的,因此你可以根据需要组合更多 fixture 或覆盖现有 fixture。有关更多信息,请参阅扩展 Test Context。
import { expect, test } from 'vitest';
const todos = [];
const archive = [];
const myTest = test.extend({
todos: async ({ task }, use) => {
todos.push(1, 2, 3);
await use(todos);
todos.length = 0;
},
archive,
});
myTest('add item', ({ todos }) => {
expect(todos.length).toBe(3);
todos.push(4);
expect(todos.length).toBe(4);
});test.skip
- 别名:
it.skip
如果你想跳过运行某些测试,但由于某种原因不想删除代码,可以使用 test.skip 来避免运行它们。
import { assert, test } from 'vitest';
test.skip('skipped test', () => {
// 测试已跳过,无错误
assert.equal(Math.sqrt(4), 3);
});你也可以通过在其上下文上动态调用 skip 来跳过测试:
import { assert, test } from 'vitest';
test('skipped test', context => {
context.skip();
// 测试已跳过,无错误
assert.equal(Math.sqrt(4), 3);
});从 Vitest 3.1 开始,如果条件未知,你可以将其作为第一个参数提供给 skip 方法:
import { assert, test } from 'vitest';
test('skipped test', context => {
context.skip(Math.random() < 0.5, 'optional message');
// 测试已跳过,无错误
assert.equal(Math.sqrt(4), 3);
});test.skipIf
- 别名:
it.skipIf
在某些情况下,你可能会在不同的环境中多次运行测试,并且其中一些测试可能是环境特定的。与其用 if 语句包装测试代码,不如使用 test.skipIf 在条件为真时跳过测试。
import { assert, test } from 'vitest';
const isDev = process.env.NODE_ENV === 'development';
test.skipIf(isDev)('prod only test', () => {
// 此测试仅在生产环境中运行
});WARNING
当使用 Vitest 作为类型检查器 时,你不能使用此语法。
test.runIf
- 别名:
it.runIf
与 test.skipIf 相反。
import { assert, test } from 'vitest';
const isDev = process.env.NODE_ENV === 'development';
test.runIf(isDev)('dev only test', () => {
// 此测试仅在开发环境中运行
});WARNING
当使用 Vitest 作为类型检查器 时,你不能使用此语法。
test.only
- 别名:
it.only
使用 test.only 仅运行给定套件中的某些测试。这在调试时非常有用。
你可以选择提供一个超时时间(毫秒),用于指定在终止前等待的时长。默认值为 5 秒,可以通过 testTimeout 进行全局配置。
import { assert, test } from 'vitest';
test.only('test', () => {
// 只有此测试(以及其他标记为 only 的测试)会运行
assert.equal(Math.sqrt(4), 2);
});有时,在特定文件中运行 only 测试非常有用,可以忽略整个测试套件中的所有其他测试,这些测试会污染输出。
为此,请运行包含相关测试的特定文件:
# vitest interesting.test.tstest.concurrent
- 别名:
it.concurrent
test.concurrent 标记连续的测试并行运行。它接收测试名称、一个包含待收集测试的异步函数以及一个可选的超时时间(毫秒)。
import { describe, test } from 'vitest';
// 两个标记为 concurrent 的测试将并行运行
describe('suite', () => {
test('serial test', async () => {
/* ... */
});
test.concurrent('concurrent test 1', async () => {
/* ... */
});
test.concurrent('concurrent test 2', async () => {
/* ... */
});
});test.skip、test.only 和 test.todo 适用于并发测试。以下所有组合均有效:
test.concurrent(/* ... */);
test.skip.concurrent(/* ... */); // 或 test.concurrent.skip(/* ... */)
test.only.concurrent(/* ... */); // 或 test.concurrent.only(/* ... */)
test.todo.concurrent(/* ... */); // 或 test.concurrent.todo(/* ... */)运行并发测试时,快照(Snapshots)和断言(Assertions)必须使用来自本地测试上下文(Test Context) 的 expect,以确保检测到正确的测试。
test.concurrent('test 1', async ({ expect }) => {
expect(foo).toMatchSnapshot();
});
test.concurrent('test 2', async ({ expect }) => {
expect(foo).toMatchSnapshot();
});WARNING
当使用 Vitest 作为类型检查器 时,你不能使用此语法。
test.sequential
- 别名:
it.sequential
test.sequential 将测试标记为顺序执行。如果你想在 describe.concurrent 中或使用 --sequence.concurrent 命令行选项时按顺序运行测试,这会很有用。
import { describe, test } from 'vitest';
// 使用配置选项 { sequence: { concurrent: true } }
test('concurrent test 1', async () => {
/* ... */
});
test('concurrent test 2', async () => {
/* ... */
});
test.sequential('sequential test 1', async () => {
/* ... */
});
test.sequential('sequential test 2', async () => {
/* ... */
});
// 在并发套件中
describe.concurrent('suite', () => {
test('concurrent test 1', async () => {
/* ... */
});
test('concurrent test 2', async () => {
/* ... */
});
test.sequential('sequential test 1', async () => {
/* ... */
});
test.sequential('sequential test 2', async () => {
/* ... */
});
});test.todo
- 别名:
it.todo
使用 test.todo 来标记待以后实现的测试。报告中会显示这些测试的条目,以便你知道还有多少测试待实现。
// 报告中将显示此测试的条目
test.todo('unimplemented test');test.fails
- 别名:
it.fails
使用 test.fails 明确指示断言将失败。
import { expect, test } from 'vitest';
function myAsyncFunc() {
return new Promise(resolve => resolve(1));
}
test.fails('fail test', async () => {
await expect(myAsyncFunc()).rejects.toBe(1);
});WARNING
当使用 Vitest 作为类型检查器 时,你不能使用此语法。
test.each
- 别名:
it.each
TIP
虽然提供了 test.each 以兼容 Jest, Vitest 还提供了 test.for,它具有集成 TestContext 的额外功能。
当你需要使用不同的变量运行相同的测试时,请使用 test.each。 你可以在测试名称中使用 printf 格式化来按测试函数参数的顺序注入参数。
%s: 字符串%d: 数字%i: 整数%f: 浮点值%j: JSON%o: 对象%#: 测试用例的 0-based 索引%$: 测试用例的 1-based 索引%%: 单个百分号 ('%')
import { expect, test } from 'vitest';
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('add(%i, %i) -> %i', (a, b, expected) => {
expect(a + b).toBe(expected);
});
// 输出如下
// ✓ add(1, 1) -> 2
// ✓ add(1, 2) -> 3
// ✓ add(2, 1) -> 3你还可以使用 $ 前缀访问对象属性和数组元素:
test.each([
{ a: 1, b: 1, expected: 2 },
{ a: 1, b: 2, expected: 3 },
{ a: 2, b: 1, expected: 3 },
])('add($a, $b) -> $expected', ({ a, b, expected }) => {
expect(a + b).toBe(expected);
});
// 输出如下
// ✓ add(1, 1) -> 2
// ✓ add(1, 2) -> 3
// ✓ add(2, 1) -> 3
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('add($0, $1) -> $2', (a, b, expected) => {
expect(a + b).toBe(expected);
});
// 输出如下
// ✓ add(1, 1) -> 2
// ✓ add(1, 2) -> 3
// ✓ add(2, 1) -> 3如果你使用对象作为参数,你还可以使用 . 访问对象属性:
test.each`
a | b | expected
${{ val: 1 }} | ${'b'} | ${'1b'}
${{ val: 2 }} | ${'b'} | ${'2b'}
${{ val: 3 }} | ${'b'} | ${'3b'}
`('add($a.val, $b) -> $expected', ({ a, b, expected }) => {
expect(a.val + b).toBe(expected);
});
// 输出如下
// ✓ add(1, b) -> 1b
// ✓ add(2, b) -> 2b
// ✓ add(3, b) -> 3b从 Vitest 0.25.3 开始,你还可以使用模板字符串表格。
- 第一行应为列名,用
|分隔; - 随后的一行或多行数据作为模板字面量表达式提供,使用
${value}语法。
import { expect, test } from 'vitest';
test.each`
a | b | expected
${1} | ${1} | ${2}
${'a'} | ${'b'} | ${'ab'}
${[]} | ${'b'} | ${'b'}
${{}} | ${'b'} | ${'[object Object]b'}
${{ asd: 1 }} | ${'b'} | ${'[object Object]b'}
`('returns $expected when $a is added $b', ({ a, b, expected }) => {
expect(a + b).toBe(expected);
});TIP
Vitest 使用 Chai 的 format 方法处理 $values。如果值被截断过多,你可以在配置文件中增加 chaiConfig.truncateThreshold。
WARNING
当使用 Vitest 作为类型检查器 时,你不能使用此语法。
test.for
- 别名:
it.for
test.each 的替代方案,用于提供 TestContext。
test.for 与 test.each 的区别在于参数中数组的提供方式。 test.for 的非数组参数(包括模板字符串用法)与 test.each 的工作方式完全相同。
// `each` 展开数组
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('add(%i, %i) -> %i', (a, b, expected) => {
expect(a + b).toBe(expected);
});
// `for` 不展开数组(注意参数周围的方括号)
test.for([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('add(%i, %i) -> %i', ([a, b, expected]) => {
expect(a + b).toBe(expected);
});第二个参数是 TestContext,例如可用于并发快照:
test.concurrent.for([
[1, 1],
[1, 2],
[2, 1],
])('add(%i, %i)', ([a, b], { expect }) => {
expect(a + b).matchSnapshot();
});bench
- 类型:
(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void
bench 定义了一个基准测试。在 Vitest 中,基准测试是一个定义一系列操作的函数。Vitest 会多次运行此函数以显示不同的性能结果。
Vitest 内部使用 tinybench 库,继承了其所有选项,这些选项可以用作第三个参数。
import { bench } from 'vitest';
bench(
'normal sorting',
() => {
const x = [1, 5, 4, 2, 3];
x.sort((a, b) => {
return a - b;
});
},
{ time: 1000 }
);export interface Options {
/**
* 运行基准测试任务所需的时间(毫秒)
* @default 500
*/
time?: number;
/**
* 即使时间选项已完成,任务也应运行的次数
* @default 10
*/
iterations?: number;
/**
* 获取当前时间戳(毫秒)的函数
*/
now?: () => number;
/**
* 用于中止基准测试的 `AbortSignal`
*/
signal?: AbortSignal;
/**
* 如果任务失败则抛出(如果为 true,事件将不起作用)
*/
throws?: boolean;
/**
* 热身时间(毫秒)
* @default 100ms
*/
warmupTime?: number;
/**
* 热身迭代次数
* @default 5
*/
warmupIterations?: number;
/**
* 在每个基准测试任务(循环)之前运行的设置函数
*/
setup?: Hook;
/**
* 在每个基准测试任务(循环)之后运行的拆卸函数
*/
teardown?: Hook;
}测试用例运行后,输出结构信息如下:
name hz min max mean p75 p99 p995 p999 rme samples
· normal sorting 6,526,368.12 0.0001 0.3638 0.0002 0.0002 0.0002 0.0002 0.0004 ±1.41% 652638export interface TaskResult {
/*
* 运行任务时抛出的最后一个错误
*/
error?: unknown;
/**
* 运行基准测试任务(循环)所需的时间(毫秒)。
*/
totalTime: number;
/**
* 样本中的最小值
*/
min: number;
/**
* 样本中的最大值
*/
max: number;
/**
* 每秒操作数
*/
hz: number;
/**
* 每次操作所需的时间(毫秒)
*/
period: number;
/**
* 每个任务迭代时间的任务样本(毫秒)
*/
samples: number[];
/**
* 样本均值/平均值(总体均值的估计)
*/
mean: number;
/**
* 样本方差(总体方差的估计)
*/
variance: number;
/**
* 样本标准差(总体标准差的估计)
*/
sd: number;
/**
* 均值的标准误差(又称样本均值抽样分布的标准差)
*/
sem: number;
/**
* 自由度
*/
df: number;
/**
* 样本的临界值
*/
critical: number;
/**
* 误差范围
*/
moe: number;
/**
* 相对误差范围
*/
rme: number;
/**
* 中位数绝对偏差
*/
mad: number;
/**
* p50/中位数百分位数
*/
p50: number;
/**
* p75 百分位数
*/
p75: number;
/**
* p99 百分位数
*/
p99: number;
/**
* p995 百分位数
*/
p995: number;
/**
* p999 百分位数
*/
p999: number;
}bench.skip
- 类型:
(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void
你可以使用 bench.skip 语法来跳过运行某些基准测试。
import { bench } from 'vitest';
bench.skip('normal sorting', () => {
const x = [1, 5, 4, 2, 3];
x.sort((a, b) => {
return a - b;
});
});bench.only
- 类型:
(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void
使用 bench.only 仅运行给定套件中的某些基准测试。这在调试时非常有用。
import { bench } from 'vitest';
bench.only('normal sorting', () => {
const x = [1, 5, 4, 2, 3];
x.sort((a, b) => {
return a - b;
});
});bench.todo
- 类型:
(name: string | Function) => void
使用 bench.todo 来标记待以后实现的基准测试。
import { bench } from 'vitest';
bench.todo('unimplemented test');describe
当你在文件的顶层使用 test 或 bench 时,它们会被收集为隐式套件的一部分。使用 describe,你可以在当前上下文中定义一个新套件,作为一组相关的测试或基准测试以及其他嵌套套件。套件允许你组织测试和基准测试,使报告更清晰。
// basic.spec.ts
// 组织测试
import { describe, expect, test } from 'vitest';
const person = {
isActive: true,
age: 32,
};
describe('person', () => {
test('person is defined', () => {
expect(person).toBeDefined();
});
test('is active', () => {
expect(person.isActive).toBeTruthy();
});
test('age limit', () => {
expect(person.age).toBeLessThanOrEqual(32);
});
});// basic.bench.ts
// 组织基准测试
import { bench, describe } from 'vitest';
describe('sort', () => {
bench('normal', () => {
const x = [1, 5, 4, 2, 3];
x.sort((a, b) => {
return a - b;
});
});
bench('reverse', () => {
const x = [1, 5, 4, 2, 3];
x.reverse().sort((a, b) => {
return a - b;
});
});
});如果你有测试或基准测试的层次结构,你也可以嵌套 describe 块:
import { describe, expect, test } from 'vitest';
function numberToCurrency(value: number | string) {
if (typeof value !== 'number') {
throw new TypeError('Value must be a number');
}
return value
.toFixed(2)
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
describe('numberToCurrency', () => {
describe('given an invalid number', () => {
test('composed of non-numbers to throw error', () => {
expect(() => numberToCurrency('abc')).toThrowError();
});
});
describe('given a valid number', () => {
test('returns the correct currency format', () => {
expect(numberToCurrency(10000)).toBe('10,000.00');
});
});
});describe.skip
- 别名:
suite.skip
在套件中使用 describe.skip 可以避免运行特定的 describe 块。
import { assert, describe, test } from 'vitest';
describe.skip('skipped suite', () => {
test('sqrt', () => {
// 套件已跳过,无错误
assert.equal(Math.sqrt(4), 3);
});
});describe.skipIf
- 别名:
suite.skipIf
在某些情况下,你可能会在不同的环境中多次运行套件,并且其中一些套件可能是环境特定的。与其用 if 语句包装套件,不如使用 describe.skipIf 在条件为真时跳过套件。
import { describe, test } from 'vitest';
const isDev = process.env.NODE_ENV === 'development';
describe.skipIf(isDev)('prod only test suite', () => {
// 此测试套件仅在生产环境中运行
});WARNING
当使用 Vitest 作为类型检查器 时,你不能使用此语法。
describe.runIf
- 别名:
suite.runIf
与 describe.skipIf 相反。
import { assert, describe, test } from 'vitest';
const isDev = process.env.NODE_ENV === 'development';
describe.runIf(isDev)('dev only test suite', () => {
// 此测试套件仅在开发环境中运行
});WARNING
当使用 Vitest 作为类型检查器 时,你不能使用此语法。
describe.only
- 类型:
(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void
使用 describe.only 仅运行某些套件。
import { assert, describe, test } from 'vitest';
// 只有此套件(以及其他标记为 only 的套件)会运行
describe.only('suite', () => {
test('sqrt', () => {
assert.equal(Math.sqrt(4), 3);
});
});
describe('other suite', () => {
// ... 将被跳过
});有时,在特定文件中运行 only 测试非常有用,可以忽略整个测试套件中的所有其他测试,这些测试会污染输出。
为此,请运行包含相关测试的特定文件:
# vitest interesting.test.tsdescribe.concurrent
- 别名:
suite.concurrent
describe.concurrent 并行运行所有内部套件和测试。
import { describe, test } from 'vitest';
// 此套件中的所有套件和测试都将并行运行
describe.concurrent('suite', () => {
test('concurrent test 1', async () => {
/* ... */
});
describe('concurrent suite 2', async () => {
test('concurrent test inner 1', async () => {
/* ... */
});
test('concurrent test inner 2', async () => {
/* ... */
});
});
test.concurrent('concurrent test 3', async () => {
/* ... */
});
});.skip、.only 和 .todo 适用于并发套件。以下所有组合均有效:
describe.concurrent(/* ... */);
describe.skip.concurrent(/* ... */); // 或 describe.concurrent.skip(/* ... */)
describe.only.concurrent(/* ... */); // 或 describe.concurrent.only(/* ... */)
describe.todo.concurrent(/* ... */); // 或 describe.concurrent.todo(/* ... */)运行并发测试时,快照(Snapshots)和断言(Assertions)必须使用来自本地测试上下文(Test Context) 的 expect,以确保检测到正确的测试。
describe.concurrent('suite', () => {
test('concurrent test 1', async ({ expect }) => {
expect(foo).toMatchSnapshot();
});
test('concurrent test 2', async ({ expect }) => {
expect(foo).toMatchSnapshot();
});
});WARNING
当使用 Vitest 作为类型检查器 时,你不能使用此语法。
describe.sequential
- 别名:
suite.sequential
套件中的 describe.sequential 将每个测试标记为顺序执行。如果你想在 describe.concurrent 中或使用 --sequence.concurrent 命令行选项时按顺序运行测试,这会很有用。
import { describe, test } from 'vitest';
describe.concurrent('suite', () => {
test('concurrent test 1', async () => {
/* ... */
});
test('concurrent test 2', async () => {
/* ... */
});
describe.sequential('', () => {
test('sequential test 1', async () => {
/* ... */
});
test('sequential test 2', async () => {
/* ... */
});
});
});describe.shuffle
- 别名:
suite.shuffle
Vitest 提供了一种通过 CLI 标志 --sequence.shuffle 或配置选项 sequence.shuffle 以随机顺序运行所有测试的方法,但如果你只想让测试套件的一部分以随机顺序运行测试,你可以使用此标志进行标记。
import { describe, test } from 'vitest';
// 或 describe('suite', { shuffle: true }, ...)
describe.shuffle('suite', () => {
test('random test 1', async () => {
/* ... */
});
test('random test 2', async () => {
/* ... */
});
test('random test 3', async () => {
/* ... */
});
// `shuffle` 是继承的
describe('still random', () => {
test('random 4.1', async () => {
/* ... */
});
test('random 4.2', async () => {
/* ... */
});
});
// 禁用内部的 shuffle
describe('not random', { shuffle: false }, () => {
test('in order 5.1', async () => {
/* ... */
});
test('in order 5.2', async () => {
/* ... */
});
});
});
// 顺序取决于配置中的 `sequence.seed` 选项(默认为 `Date.now()`).skip、.only 和 .todo 适用于随机套件。
WARNING
当使用 Vitest 作为类型检查器 时,你不能使用此语法。
describe.todo
- 别名:
suite.todo
使用 describe.todo 来标记待以后实现的套件。报告中会显示这些套件的条目,以便你知道还有多少套件待实现。
// 报告中将显示此套件的条目
describe.todo('unimplemented suite');describe.each
- 别名:
suite.each
TIP
虽然提供了 describe.each 以兼容 Jest, Vitest 还提供了 describe.for,它简化了参数类型并与 test.for 对齐。
如果你有多个测试依赖于相同的数据,请使用 describe.each。
import { describe, expect, test } from 'vitest';
describe.each([
{ a: 1, b: 1, expected: 2 },
{ a: 1, b: 2, expected: 3 },
{ a: 2, b: 1, expected: 3 },
])('describe object add($a, $b)', ({ a, b, expected }) => {
test(`returns ${expected}`, () => {
expect(a + b).toBe(expected);
});
test(`returned value not be greater than ${expected}`, () => {
expect(a + b).not.toBeGreaterThan(expected);
});
test(`returned value not be less than ${expected}`, () => {
expect(a + b).not.toBeLessThan(expected);
});
});从 Vitest 0.25.3 开始,你还可以使用模板字符串表格。
- 第一行应为列名,用
|分隔; - 随后的一行或多行数据作为模板字面量表达式提供,使用
${value}语法。
import { describe, expect, test } from 'vitest';
describe.each`
a | b | expected
${1} | ${1} | ${2}
${'a'} | ${'b'} | ${'ab'}
${[]} | ${'b'} | ${'b'}
${{}} | ${'b'} | ${'[object Object]b'}
${{ asd: 1 }} | ${'b'} | ${'[object Object]b'}
`('describe template string add($a, $b)', ({ a, b, expected }) => {
test(`returns ${expected}`, () => {
expect(a + b).toBe(expected);
});
});WARNING
当使用 Vitest 作为类型检查器 时,你不能使用此语法。
describe.for
- 别名:
suite.for
与 describe.each 的区别在于参数中数组用例的提供方式。 其他非数组用例(包括模板字符串用法)的工作方式完全相同。
// `each` 展开数组用例
describe.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('add(%i, %i) -> %i', (a, b, expected) => {
test('test', () => {
expect(a + b).toBe(expected);
});
});
// `for` 不展开数组用例
describe.for([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('add(%i, %i) -> %i', ([a, b, expected]) => {
test('test', () => {
expect(a + b).toBe(expected);
});
});设置和拆卸
这些函数允许你钩入测试的生命周期中,以避免重复设置和拆卸代码。它们适用于当前上下文:如果它们在顶层使用,则适用于文件;如果它们在 describe 块内部,则适用于当前套件。当你将 Vitest 作为类型检查器运行时,这些钩子不会被调用。
beforeEach
- 类型:
beforeEach(fn: () => Awaitable<void>, timeout?: number)
注册一个回调函数,在当前上下文中每个测试运行之前调用。 如果函数返回一个 Promise,Vitest 会等待 Promise 解析后再运行测试。
你可以选择传递一个超时时间(毫秒),定义在终止前等待的时长。默认值为 5 秒。
import { beforeEach } from 'vitest';
beforeEach(async () => {
// 在每次测试运行前清除模拟并添加一些测试数据
await stopMocking();
await addUser({ name: 'John' });
});在这里,beforeEach 确保在每次测试之前都添加了用户。
beforeEach 也接受一个可选的清理函数(等同于 afterEach)。
import { beforeEach } from 'vitest';
beforeEach(async () => {
// 在每次测试运行前调用一次
await prepareSomething();
// 清理函数,在每次测试运行后调用一次
return async () => {
await resetSomething();
};
});afterEach
- 类型:
afterEach(fn: () => Awaitable<void>, timeout?: number)
注册一个回调函数,在当前上下文中每个测试完成后调用。 如果函数返回一个 Promise,Vitest 会等待 Promise 解析后再继续。
你可以选择提供一个超时时间(毫秒),用于指定在终止前等待的时长。默认值为 5 秒。
import { afterEach } from 'vitest';
afterEach(async () => {
await clearTestingData(); // 在每次测试运行后清除测试数据
});在这里,afterEach 确保在每次测试运行后清除测试数据。
TIP
Vitest 1.3.0 添加了 onTestFinished 钩子。你可以在测试执行期间调用它,以便在测试运行结束后清理任何状态。
beforeAll
- 类型:
beforeAll(fn: () => Awaitable<void>, timeout?: number)
注册一个回调函数,在当前上下文中所有测试开始运行之前调用一次。 如果函数返回一个 Promise,Vitest 会等待 Promise 解析后再运行测试。
你可以选择提供一个超时时间(毫秒),用于指定在终止前等待的时长。默认值为 5 秒。
import { beforeAll } from 'vitest';
beforeAll(async () => {
await startMocking(); // 在所有测试运行前调用一次
});在这里,beforeAll 确保在测试运行之前设置好模拟数据。
beforeAll 也接受一个可选的清理函数(等同于 afterAll)。
import { beforeAll } from 'vitest';
beforeAll(async () => {
// 在所有测试运行前调用一次
await startMocking();
// 清理函数,在所有测试运行后调用一次
return async () => {
await stopMocking();
};
});afterAll
- 类型:
afterAll(fn: () => Awaitable<void>, timeout?: number)
注册一个回调函数,在当前上下文中所有测试运行完毕后调用一次。 如果函数返回一个 Promise,Vitest 会等待 Promise 解析后再继续。
你可以选择提供一个超时时间(毫秒),用于指定在终止前等待的时长。默认值为 5 秒。
import { afterAll } from 'vitest';
afterAll(async () => {
await stopMocking(); // 此方法在所有测试运行后调用
});在这里,afterAll 确保在所有测试运行后调用 stopMocking 方法。
测试钩子
Vitest 提供了一些钩子,你可以在测试执行_期间_调用它们,以便在测试运行结束后清理状态。
WARNING
如果在测试体之外调用这些钩子,它们将抛出错误。
onTestFinished
此钩子总是在测试运行结束后调用。它在 afterEach 钩子之后调用,因为它们可能会影响测试结果。它接收一个 ExtendedContext 对象,就像 beforeEach 和 afterEach 一样。
import { onTestFinished, test } from 'vitest';
test('performs a query', () => {
const db = connectDb();
onTestFinished(() => db.close());
db.query('SELECT * FROM users');
});WARNING
如果你正在并发运行测试,你应该始终使用测试上下文中的 onTestFinished 钩子,因为 Vitest 不会在全局钩子中跟踪并发测试:
import { test } from 'vitest';
test.concurrent('performs a query', ({ onTestFinished }) => {
const db = connectDb();
onTestFinished(() => db.close());
db.query('SELECT * FROM users');
});此钩子在创建可重用逻辑时特别有用:
// 这可以放在单独的文件中
function getTestDb() {
const db = connectMockedDb();
onTestFinished(() => db.close());
return db;
}
test('performs a user query', async () => {
const db = getTestDb();
expect(await db.query('SELECT * from users').perform()).toEqual([]);
});
test('performs an organization query', async () => {
const db = getTestDb();
expect(await db.query('SELECT * from organizations').perform()).toEqual([]);
});TIP
此钩子总是以相反的顺序调用,并且不受 sequence.hooks 选项的影响。
onTestFailed
此钩子仅在测试失败后调用。它在 afterEach 钩子之后调用,因为它们可能会影响测试结果。它接收一个 ExtendedContext 对象,就像 beforeEach 和 afterEach 一样。此钩子对于调试很有用。
import { onTestFailed, test } from 'vitest';
test('performs a query', () => {
const db = connectDb();
onTestFailed(({ task }) => {
console.log(task.result.errors);
});
db.query('SELECT * FROM users');
});WARNING
如果你正在并发运行测试,你应该始终使用测试上下文中的 onTestFailed 钩子,因为 Vitest 不会在全局钩子中跟踪并发测试:
import { test } from 'vitest';
test.concurrent('performs a query', ({ onTestFailed }) => {
const db = connectDb();
onTestFailed(({ task }) => {
console.log(task.result.errors);
});
db.query('SELECT * FROM users');
});