特性
- Vite 的配置、转换器、解析器和插件支持
- 使用与应用相同的配置来运行测试!
- 智能且即时的观察模式,类似于测试中的 HMR!
- 支持 Vue、React、Svelte、Lit、Marko 等的组件测试
- 开箱即用的 TypeScript / JSX 支持
- ESM 优先,支持顶级 await
- 通过 Tinypool 实现多线程 Workers
- 通过 Tinybench 支持基准测试
- 支持对测试套件和测试进行过滤、超时设置和并发运行
- 项目支持
- 兼容 Jest 的快照
- 内置 Chai 用于断言 + Jest expect 兼容的 API
- 内置 Tinyspy 用于模拟
- happy-dom 或 jsdom 用于 DOM 模拟
- 浏览器模式支持在浏览器中运行组件测试
- 通过 v8 或 istanbul 进行代码覆盖率统计
- 类似 Rust 的源码内测试
- 通过 expect-type 进行类型测试
- 分片支持
- 报告未处理的错误
测试、开发和构建之间的共享配置
Vitest 能够复用 Vite 的配置、转换器、解析器和插件。这意味着你可以使用与应用相同的配置来运行测试。
在 配置 Vitest 中了解更多。
观察模式
$ vitest
当你修改源代码或测试文件时,Vitest 会智能分析模块依赖图,仅重新运行相关测试,类似于 Vite 中的 HMR!
在开发环境中,vitest
默认以 watch mode
启动;在 CI 环境中(当 process.env.CI
存在时),则智能地以 run mode
启动。你可以使用 vitest watch
或 vitest run
来明确指定所需的模式。
使用 --standalone
标志启动 Vitest,使其在后台运行。它不会运行任何测试,直到相关文件发生变化。如果源代码发生变化,Vitest 不会运行测试,除非导入该源代码的测试已被执行。
开箱即用的常见 Web 惯用法
开箱即用地支持 ES Module / TypeScript / JSX / PostCSS。
线程
默认情况下,Vitest 通过 Tinypool(Piscina 的轻量级分支)使用 node:child_process
在多个进程中运行测试文件,从而支持测试并行执行。如果你想进一步加快测试套件的速度,可以考虑启用 --pool=threads
来使用 node:worker_threads
运行测试(请注意,某些包可能不适用于此设置)。
要在单个线程或进程中运行测试,请参阅 poolOptions
。
Vitest 还会隔离每个文件的运行环境,以确保一个文件中的环境修改不会影响其他文件。可以通过向 CLI 传递 --no-isolate
来禁用隔离(牺牲正确性换取运行性能)。
测试过滤
Vitest 提供了多种方法来筛选要执行的测试用例,以加快测试速度,让你能够专注于开发。
在 测试过滤 中了解更多。
并发运行测试
在连续的测试中使用 .concurrent
,使其并行启动。
import { describe, it } from 'vitest';
// 两个标记为 concurrent 的测试将并行启动
describe('suite', () => {
it('serial test', async () => {
/* ... */
});
it.concurrent('concurrent test 1', async ({ expect }) => {
/* ... */
});
it.concurrent('concurrent test 2', async ({ expect }) => {
/* ... */
});
});
如果你在一个测试套件上使用 .concurrent
,那么该套件中的所有测试都将并行执行。
import { describe, it } from 'vitest';
// 此套件中的所有测试都将并行启动
describe.concurrent('suite', () => {
it('concurrent test 1', async ({ expect }) => {
/* ... */
});
it('concurrent test 2', async ({ expect }) => {
/* ... */
});
it.concurrent('concurrent test 3', async ({ expect }) => {
/* ... */
});
});
你还可以将 .skip
、.only
和 .todo
与并发套件和测试一起使用。在 API 参考 中阅读更多。
WARNING
运行并发测试时,快照和断言必须使用来自本地 测试上下文 的 expect
,以确保正确识别对应的测试。
快照
兼容 Jest 的 快照支持。
import { expect, it } from 'vitest';
it('renders correctly', () => {
const result = render();
expect(result).toMatchSnapshot();
});
在 快照 中了解更多。
Chai 和 Jest expect
兼容性
Chai 内置用于断言,并提供与 Jest expect
兼容的 API。
请注意,如果你正在使用添加匹配器的第三方库,将 test.globals
设置为 true
将提供更好的兼容性。
模拟
Tinyspy 内置用于模拟,并在 vi
对象上提供 jest
兼容的 API。
import { expect, vi } from 'vitest';
const fn = vi.fn();
fn('hello', 1);
expect(vi.isMockFunction(fn)).toBe(true);
expect(fn.mock.calls[0]).toEqual(['hello', 1]);
fn.mockImplementation((arg: string) => arg);
fn('world', 2);
expect(fn.mock.results[1].value).toBe('world');
Vitest 支持使用 happy-dom 或 jsdom 来模拟 DOM 和浏览器 API。它们不随 Vitest 一起提供,你需要单独安装它们:
$ npm i -D happy-dom
$ npm i -D jsdom
之后,在你的配置文件中更改 environment
选项:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'happy-dom', // 或 'jsdom', 'node'
},
});
在 模拟 中了解更多。
覆盖率
Vitest 支持通过 v8
进行原生代码覆盖率统计,以及通过 istanbul
进行插桩代码覆盖率统计。
{
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage"
}
}
在 覆盖率 中了解更多。
源码内测试
Vitest 还提供了一种在源代码中与实现一起运行测试的方法,类似于 Rust 的模块测试。
这使得测试能够与实现共享相同的闭包,并且在不导出的情况下测试私有状态。同时,这也使得开发过程中的反馈循环更加紧密。
// 实现
export function add(...args: number[]): number {
return args.reduce((a, b) => a + b, 0);
}
// 源码内测试套件
if (import.meta.vitest) {
const { it, expect } = import.meta.vitest;
it('add', () => {
expect(add()).toBe(0);
expect(add(1)).toBe(1);
expect(add(1, 2, 3)).toBe(6);
});
}
在 源码内测试 中了解更多。
基准测试 实验性
你可以通过 Tinybench 使用 bench
函数运行基准测试,以比较性能结果。
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;
});
});
});
类型测试 实验性
你可以编写测试来捕获类型回归问题。Vitest 附带 expect-type
包,为你提供类似且易于理解的 API。
import { assertType, expectTypeOf, test } from 'vitest';
import { mount } from './mount.js';
test('my types work properly', () => {
expectTypeOf(mount).toBeFunction();
expectTypeOf(mount).parameter(0).toMatchTypeOf<{ name: string }>();
// @ts-expect-error name is a string
assertType(mount({ name: 42 }));
});
分片
使用 --shard
和 --reporter=blob
标志在不同的机器上运行测试。 所有测试和覆盖率结果都可以在 CI 流水线结束时,使用 --merge-reports
命令进行合并:
vitest --shard=1/2 --reporter=blob --coverage
vitest --shard=2/2 --reporter=blob --coverage
vitest --merge-reports --reporter=junit --coverage
有关更多信息,请参阅 提高性能 | 分片
。
环境变量
Vitest 专门从 .env
文件中自动加载以 VITE_
为前缀的环境变量,以保持与前端相关测试的兼容性,并遵循 Vite 既定的约定。若要从 .env
文件中加载所有环境变量,你可以使用从 vite
导入的 loadEnv
方法:
import { loadEnv } from 'vite';
import { defineConfig } from 'vitest/config';
export default defineConfig(({ mode }) => ({
test: {
// mode 定义了在存在时,应选择哪个 ".env.{mode}" 文件
env: loadEnv(mode, process.cwd(), ''),
},
}));
未处理的错误
默认情况下,Vitest 会捕获并报告所有未处理的拒绝、未捕获的异常(在 Node.js 中)以及 错误 事件(在浏览器中)。
你可以通过手动捕获这些错误来禁用此行为。Vitest 假定回调已由你处理,因此不会报告该错误。
// 在 Node.js 中
process.on('unhandledRejection', () => {
// 你自己的处理程序
});
process.on('uncaughtException', () => {
// 你自己的处理程序
});
// 在浏览器中
window.addEventListener('error', () => {
// 你自己的处理程序
});
window.addEventListener('unhandledrejection', () => {
// 你自己的处理程序
});
或者,你也可以使用 dangerouslyIgnoreUnhandledErrors
选项来忽略已报告的错误。Vitest 仍然会报告这些错误,但它们不会影响测试结果(退出代码不会改变)。
如果你需要测试错误未被捕获,你可以创建一个如下所示的测试:
test('my function throws uncaught error', async ({ onTestFinished }) => {
onTestFinished(() => {
// 如果在测试期间从未调用过该事件,
// 请确保在下一个测试开始之前将其移除。
process.removeAllListeners('unhandledrejection');
});
return new Promise((resolve, reject) => {
process.once('unhandledrejection', error => {
try {
expect(error.message).toBe('my error');
resolve();
} catch (error) {
reject(error);
}
});
callMyFunctionThatRejectsError();
});
});