第2章 功能特性
功能特性 支持 Vite 的配置、转换器和解析器以及插件,直接复用应用中的配置来运行测试! 拥有智能即时的 watch 模式,类似 Vite 的 HMR 机制,专为测试设计! 支持 Vue、React、Svelte、Lit、Marko 等框架的组件测试。 开箱即用的 TypeScript 和 JSX 支持。 以 ESM 为优先,支持顶级 await。 基于 Tinybench 的性能基准测试支持。 支持过滤、超时以及测试套件和用例的并发执行。 多项目支持。 兼容 Jest 的快照功能。 内置 Chai 断言库,并提供兼容 Jest expect 的 API。 兼容 Jest 的 Mock 机制。 可使用 happy-dom 或 jsdom 进行 DOM 模拟。 浏览器模式,允许在浏览器中运行组件测试。 通过 v8 或 istanbul 生成代码覆盖率报告。 类似 Rust 的源码内测试。 基于 expect-type 的类型测试。 分片支持。 未捕获错误报告。 提供视频教程,教你编写第一个测试。
提示:本页介绍了 Vitest 的核心能力概览。如果你是 Vitest 的新手,建议先阅读学习教程,以获取初步的介绍。
测试、开发与构建共享配置 复用 Vite 的配置、转换器、解析器和插件。你可以直接使用应用中的配置来运行测试。 了解更多:配置 Vitest。
Watch 模式 ```bash $ vitest ``` 当你修改源代码或测试文件时,Vest 会智能地搜索模块依赖图,并只重新运行相关的测试,其机制与 Vite 中的 HMR 类似! 在开发环境下,vitest 默认启动 watch 模式;而在 CI 环境(即存在 process.env.CI 时),则默认以 run 模式运行。你可以使用 vitest watch 或 vitest run 显式指定运行模式。 使用 --standalone 标志启动 Vitest,可使其在后台持续运行。在测试发生变化之前,它不会执行任何测试。如果源代码发生变化,但导入该源代码的测试尚未运行过,Vitest 也不会触发测试。
开箱即用的通用 Web 规范 开箱即用的 ES Module、TypeScript、JSX 和 PostCSS 支持。
线程与进程 默认情况下,Vitest 使用 node:child_process 在多个进程中运行测试文件,允许测试同时执行。如果你想进一步加速测试套件,可以考虑启用 --pool=threads,使用 node:worker_threads 来运行测试(请注意,某些包可能不兼容此配置)。若要使用单线程或单进程运行测试,请参阅 fileParallelism。 Vitest 还会隔离每个文件的运行环境,确保一个文件中的环境变量变更不会影响其他文件。可以通过向 CLI 传递 --no-isolate 来禁用隔离(这将牺牲正确性以换取运行性能)。
测试过滤 Vitest 提供了多种方式来筛选要运行的测试,从而加速测试流程,让你能更专注于开发。 了解更多:测试过滤。
并发运行测试 在顺序测试中使用 .concurrent 即可并行启动它们。 ```ts 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,该套件内的所有测试都将并行启动。 ```ts 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 参考。 警告:在运行并发测试时,快照和断言必须使用本地测试上下文中的 expect,以确保识别到正确的测试。
快照 兼容 Jest 的快照支持。 ```ts import { expect, it } from 'vitest'
it('renders correctly', () => { const result = render() expect(result).toMatchSnapshot() }) ``` 了解更多:快照。
Chai 与 Jest expect 的兼容性 内置 Chai 用于断言,并提供兼容 Jest expect 的 API。 注意:如果你使用的是添加了自定义 matcher 的第三方库,将 test.globals 设置为 true 可以提供更好的兼容性。
模拟(Mocking) Vitest 在 vi 对象上提供了兼容 Jest 的 API。 ```ts 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 中,你需要单独安装它们: ```bash $ npm i -D happy-dom $ npm i -D jsdom ``` 安装完成后,在配置文件中修改 environment 选项: ```ts // vitest.config.ts import { defineConfig } from 'vitest/config'
export default defineConfig({ test: { environment: 'happy-dom', // 或 'jsdom', 'node' }, }) ``` 了解更多:模拟。
覆盖率 Vitest 支持通过 v8 进行的原生代码覆盖率以及通过 istanbul 进行的插桩代码覆盖率。 ```json { "scripts": { "test": "vitest", "coverage": "vitest run --coverage" } } ``` 了解更多:覆盖率。
源码内测试 Vitest 还提供了一种在源代码中直接运行测试的方法,测试代码与实现代码放在一起,类似于 Rust 的模块测试。 这使得测试能够与实现共享同一个闭包,从而无需导出即可测试私有状态。同时,这也缩短了开发中的反馈循环。 ```ts // src/index.ts
// 实现 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) }) } ``` 了解更多:源码内测试。
基准测试 你可以通过测试上下文中的 bench fixture 配合 Tinybench 运行基准测试,以比较性能结果。 ```ts // sort.bench.ts import { test } from 'vitest'
test('sort', async ({ bench }) => { await bench.compare( 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。 ```ts // types.test-d.ts import { assertType, expectTypeOf, test } from 'vitest' import { mount } from './mount.js'
test('my types work properly', () => { expectTypeOf(mount).toBeFunction() expectTypeOf(mount).parameter(0).toExtend<{ name: string }>()
// @ts-expect-error name is a string assertType(mount({ name: 42 })) }) ```
分片(Sharding) 使用 --shard 和 --reporter=blob 标志在不同机器上运行测试。所有测试和覆盖率结果都可以在 CI 流水线末端使用 --merge-reports 命令进行合并: ```bash vitest --shard=1/2 --reporter=blob --coverage vitest --shard=2/2 --reporter=blob --coverage vitest --merge-reports --reporter=junit --coverage ``` 更多信息请参阅:提升性能 | 分片。
环境变量 为了保持与前端相关测试的兼容性并遵循 Vite 的既定惯例,Vitest 专门从 .env 文件中自动加载以 VITE_ 为前缀的环境变量。如果你仍希望加载 .env 文件中的所有环境变量,可以使用从 vite 导入的 loadEnv 方法: ```ts // vitest.config.ts import { loadEnv } from 'vite' import { defineConfig } from 'vitest/config'
export default defineConfig(({ mode }) => ({ test: { // mode 定义了如果存在则选择哪个 ".env.{mode}" 文件 env: loadEnv(mode, process.cwd(), ''), }, })) ```
未处理的错误 默认情况下,Vitest 会捕获并报告所有未处理的拒绝(unhandled rejections)、未捕获的异常(在 Node.js 中)以及错误事件(在浏览器中)。 你可以通过手动捕获这些事件来禁用此行为。Vitest 会假设回调已由你处理,因此不会报告该错误。 ```js // setup.node.js // 在 Node.js 中 process.on('unhandledRejection', () => { // 你的自定义处理器 })
process.on('uncaughtException', () => { // 你的自定义处理器 }) ``` ```ts // setup.browser.js // 在浏览器中 window.addEventListener('error', () => { // 你的自定义处理器 })
window.addEventListener('unhandledrejection', () => { // 你的自定义处理器 }) ``` 或者,你也可以使用 dangerouslyIgnoreUnhandledErrors 选项来忽略报告的错误。Vitest 仍会报告这些错误,但它们不会影响测试结果(退出码不会改变)。 如果你需要测试某个错误是否未被捕获,可以编写如下测试: ```ts test('my function throws uncaught error', async ({ onTestFinished }) => { const unhandledRejectionListener = vi.fn() process.on('unhandledRejection', unhandledRejectionListener) onTestFinished(() => { process.off('unhandledRejection', unhandledRejectionListener) })
callMyFunctionThatRejectsError()
await expect.poll(unhandledRejectionListener).toHaveBeenCalled() }) ```