AI 参与说明(Agent:Codex):本文由 Codex 基于 Vitest 官方 v4.1 文档辅助整理,重点核对适用场景、Test Environment、Browser Mode、Mocking、Coverage、Type Testing、Test Projects 与 CI 能力。资料核验于 2026-08-26,Vitest 官方首页当日显示稳定版为 v4.1.11。最小示例已使用 Node.js 26.0.0、pnpm 10.14.0、Vitest 4.1.11 与 TypeScript 5.9.2 实际运行;读者仍应以项目 lockfile 和目标运行时为准。
适用范围:本文面向准备为 JavaScript / TypeScript 项目建立测试体系,或评估从 Jest 迁移到 Vitest 的开发者。Vitest 4 要求 Node.js 20+ 与 Vite 6+;即使项目不直接使用 Vite,也可以用 Vitest 测试 Node.js 代码。Getting Started Migrating to Vitest 4.0
结论#
如果项目以 JavaScript / TypeScript 为主,尤其已经使用 Vite、React、Vue 或 Svelte,Vitest 很适合作为默认的 Unit Testing 与轻量 Integration Testing 工具。它复用 Vite 的 Plugin、Alias、Module Resolution 与 Transformation Pipeline,提供 Jest-compatible 的 expect、Mock、Snapshot 和 Coverage API,并通过 Watch Mode 只重跑受改动影响的测试。Why Vitest Features
但 Vitest 不应被理解成“一个工具包办所有测试”:
- 纯函数、业务规则、状态转换、Node.js Service 与 SDK,优先使用默认
node环境。 - UI Component 的轻量 DOM 测试可以使用
jsdom或happy-dom;涉及真实 CSS、Focus、Event Propagation 或 Browser API 时,使用 Browser Mode。 - 完整登录、跨页面导航、支付、下载、多标签页或部署后验证,仍应由 Playwright、Cypress 等独立 End-to-End Testing 工具负责。
- TypeScript 文件能被执行,不代表已经通过 Type Checking;应保留
tsc --noEmit,或另行启用 Vitest Type Testing。
Vitest 最直接的效果不是“证明系统没有 Bug”,而是把重要行为写成可重复执行的证据:开发期缩短修改后的反馈时间,重构时尽早发现 Regression,CI 中用退出码、Coverage Threshold 与机器可读 Report 阻止已知问题重新进入主分支。
场景决策表#
| 场景 | 是否适合 | 推荐方式 | 能达到的效果 |
|---|---|---|---|
| 纯函数、价格计算、权限规则、解析与数据转换 | 很适合 | node 环境的 Unit Test | 快速覆盖正常输入、边界值和错误分支 |
| Node.js Service、SDK、Repository | 适合 | Mock 外部边界,并保留关键真实 Integration Test | 验证模块协作、错误传播与资源清理 |
| Vite 驱动的 React / Vue / Svelte 应用 | 很适合 | 复用 Vite Config 与 Framework Plugin | 减少开发、构建和测试之间的转换差异 |
| Component 状态、表单与基本 DOM 交互 | 适合 | jsdom / happy-dom,或 Browser Mode | 验证渲染、交互、Loading 与 Error State |
| CSS Layout、Focus、Accessibility、真实 Browser API | 适合,但必须选对环境 | Browser Mode + Playwright Provider | 发现 DOM 模拟器无法覆盖的浏览器行为 |
| TypeScript 公共 API、Generic 与类型推断 | 适合 | *.test-d.ts + expectTypeOf / assertType | 把编译期 Type Contract 变成 Regression Test |
| Monorepo、多个 Package、Node 与 Browser 混合测试 | 适合 | test.projects | 用独立 Config 管理多种 Test Strategy |
| 大型 CI Test Suite | 适合 | Worker Parallelism、Filter、Shard、Coverage 与 Reporter | 缩短 CI 时间,并生成 JUnit / JSON / HTML 结果 |
| 稳定的小型结构化输出 | 有条件适合 | Snapshot,并审阅每次 Snapshot Diff | 发现非预期结构变化 |
| 完整 End-to-End User Journey | 只能补充 | Vitest 负责 Unit / Component,Playwright / Cypress 负责 E2E | 覆盖真实部署、页面跳转与 Browser Context |
| 非 JavaScript / TypeScript 项目 | 不适合 | 使用对应语言的 Testing Framework | 避免引入无关 Toolchain |
Vitest 到底负责什么#
Vitest 首先是 Test Runner,但它把常见测试能力组合到了一条工程链路中:
- 根据
*.test.*、*.spec.*或自定义 Pattern 发现 Test File。 - 通过 Vite 的 Transformation Pipeline 处理 ESM、TypeScript、JSX、Alias 与 Plugin。
- 在 Worker 中执行 Test File;不同文件默认并行,同一文件内的 Test 默认顺序执行。
- 用
expect进行 Assertion,用vi创建 Mock、Spy、Fake Timer 和 Stub。 - 输出 Terminal、JUnit、JSON、HTML、Coverage 或 Vitest UI 结果。
| 能力 | 常用 API / 配置 | 解决的问题 |
|---|---|---|
| Test 与 Suite | test、it、describe、test.each | 描述 Example、分组与参数化 Case |
| Assertion | expect、.resolves、.rejects、expectTypeOf | 验证值、错误、异步结果与 Type Contract |
| Lifecycle | beforeEach、afterEach、beforeAll、afterAll | 建立并回收 Test Resource |
| Test Double | vi.fn、vi.spyOn、vi.mock | 隔离 Network、Clock、Environment 与外部 Service |
| Time Control | vi.useFakeTimers、vi.setSystemTime | 稳定测试 Timeout、Retry、Debounce 与日期逻辑 |
| Snapshot | toMatchSnapshot、toMatchInlineSnapshot、toMatchFileSnapshot | 固化稳定的结构化输出 |
| Environment | node、jsdom、happy-dom、Browser Mode | 在不同 Runtime Fidelity 与速度之间取舍 |
| Coverage | V8 / Istanbul Provider、Threshold | 找出没有执行到的 Statement、Branch 与 Function |
| Scale | Watch、Filter、Test Tags、Projects、Shard | 控制大仓库的反馈时间与 CI 负载 |
| Report | Default、JUnit、JSON、HTML、Vitest UI | 为开发者、CI 与外部平台提供结果 |
Vitest 还提供 Benchmarking,但官方将其标记为 Experimental。它适合比较同一环境中的实现差异,不应替代 Production Profiling 或完整的 Performance Testing。Benchmarking
先选对 Test Environment#
环境选错会让 Test 看似通过,却没有验证真正关心的 Runtime Behavior。
| 环境 | 特征 | 适合场景 | 主要边界 |
|---|---|---|---|
node | 默认、启动快,没有 DOM | 业务逻辑、Node.js Service、CLI、SDK | 不能证明 Browser API 与 UI Behavior |
jsdom | 在 Node.js 中模拟较完整 DOM | Component 的基本 Render、Form 与 Event | 没有真实 Layout Engine,可能出现 False Positive / Negative |
happy-dom | 更轻量的 DOM 模拟 | API 需求较少、追求启动速度的 Component Test | Web API 覆盖通常少于真实 Browser |
| Browser Mode | Test 原生运行在 Browser | CSS、Focus、Accessibility、Canvas、真实 Event 与 Browser API | 初始化更慢,官方仍建议 Critical Flow 补充独立 Browser Test Runner |
Vitest 官方目前推荐用 Browser Mode 做 Component Testing,因为真实 Browser 能覆盖 CSS Layout、Event Propagation、Focus Management 与 Accessibility 等模拟环境容易遗漏的行为。Why Browser Mode Component Testing
Browser Mode 不是 environment: "browser"。它需要 Provider,并通过独立的 browser 配置启用:
pnpm add -D @vitest/browser-playwright@4.1.11 playwright
pnpm exec playwright install chromium// vitest.config.ts
import { playwright } from "@vitest/browser-playwright";
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: "chromium" }],
},
},
});可以用 vitest init browser 生成初始配置。CI 中应使用 Playwright 或 WebdriverIO Provider;官方把 Preview Provider 定位为预览用途,并推荐没有既有选择时优先采用支持并行执行的 Playwright Provider。Browser Mode
最小可复现示例#
下面用一个报价模块同时展示同步 Assertion、Async Test、vi.fn、vi.spyOn、Error Test 与 Coverage Threshold。
1. 前置条件与安装#
示例按 Vitest 4.1.11 编写,要求 Node.js 20+。已有 Vite 项目还需要确认 Vite 为 6+。
pnpm add -D vitest@4.1.11 @vitest/coverage-v8@4.1.11 typescript@5.9.2在 package.json 中加入:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}pnpm test用于本地开发,在交互终端默认进入 Watch Mode。pnpm test:run只执行一次后退出,适合 CI。pnpm test:coverage执行一次并生成 Coverage Report。
2. 配置 Vitest#
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["src/**/*.test.ts"],
environment: "node",
clearMocks: true,
restoreMocks: true,
coverage: {
provider: "v8",
include: ["src/**/*.ts"],
exclude: ["src/**/*.test.ts", "src/**/*.test-d.ts"],
reporter: ["text", "html"],
thresholds: {
statements: 90,
branches: 80,
functions: 90,
lines: 90,
},
},
},
});这里有三个重要取舍:
- Vitest 4 默认只在 Coverage Report 中展示 Test Run 实际加载的文件。显式设置
coverage.include,才能让完全没有被 Test 导入的源文件以 0% 出现在 Report 中。 - 不设置
coverage.enabled: true,避免本地普通 Watch Run 每次都承担 Coverage 开销;只在test:coverage中用--coverage开启。 90 / 80 / 90 / 90只是演示 Threshold 的写法,不是通用质量标准。现有项目应先测出 Baseline,再逐步提高,而不是为了数字编写没有行为价值的 Assertion。
Vitest 4 已移除旧教程常见的 coverage.all。升级时应删除该项,并用 coverage.include 明确 Source Scope。Coverage Coverage Config Vitest 4 Migration
3. 编写待测模块#
// src/pricing.ts
export interface PriceClient {
getPrice(sku: string): Promise<number>;
}
export interface AuditLog {
record(message: string): void;
}
export function calculateSubtotal(
unitPrice: number,
quantity: number,
): number {
return unitPrice * quantity;
}
export async function createQuote(
client: PriceClient,
audit: AuditLog,
sku: string,
quantity: number,
) {
const unitPrice = await client.getPrice(sku);
const total = calculateSubtotal(unitPrice, quantity);
audit.record(`quoted:${sku}:${total}`);
return {
sku,
quantity,
unitPrice,
total,
};
}4. 编写 Test#
// src/pricing.test.ts
import { describe, expect, it, vi } from "vitest";
import { calculateSubtotal, createQuote } from "./pricing";
describe("calculateSubtotal", () => {
it("calculates a subtotal", () => {
expect(calculateSubtotal(25, 2)).toBe(50);
});
});
describe("createQuote", () => {
it("reads a price and records an audit event", async () => {
const getPrice = vi.fn(async (_sku: string) => 25);
const audit = {
record(_message: string): void {},
};
const recordSpy = vi.spyOn(audit, "record");
await expect(
createQuote({ getPrice }, audit, "sku-1", 2),
).resolves.toEqual({
sku: "sku-1",
quantity: 2,
unitPrice: 25,
total: 50,
});
expect(getPrice).toHaveBeenCalledOnce();
expect(getPrice).toHaveBeenCalledWith("sku-1");
expect(recordSpy).toHaveBeenCalledWith("quoted:sku-1:50");
});
it("propagates service errors without writing an audit event", async () => {
const getPrice = vi.fn(async () => {
throw new Error("price service unavailable");
});
const audit = { record: vi.fn() };
await expect(
createQuote({ getPrice }, audit, "sku-1", 2),
).rejects.toThrow("price service unavailable");
expect(audit.record).not.toHaveBeenCalled();
});
});这个 Test 刻意先用 Dependency Injection,而不是 vi.mock 整个 Module:
vi.fn创建可控的PriceClientTest Double,并记录调用次数与参数。vi.spyOn观察已有 Method,默认仍执行原 Implementation。.resolves与.rejects分别验证 Async Success 和 Failure。- 失败场景额外断言 Audit 没有写入,避免只检查 Error Message 而漏掉 Side Effect。
vi.mock 适合无法直接注入的 Module Boundary,但它会被提升到 Import 之前;复杂 Mock 还受 ESM 与 Browser Mode 限制。优先通过清晰的 Interface 与 Dependency Injection 缩小 Mock 范围,通常更容易理解和维护。Mocking Mocking Modules
5. 运行与验证#
pnpm test:run
pnpm test:coverage预期核心结果:
Test Files 1 passed (1)
Tests 3 passed (3)
Statements : 100% (5/5)
Branches : 100% (0/0)
Functions : 100% (2/2)
Lines : 100% (5/5)这里的 100% 只表示这个小例子中的可执行代码被执行,并不证明设计、断言或业务需求完整。
已有 Vite Config 时如何处理#
Vitest 默认读取 vite.config.*,所以已有的 Alias、React Plugin 与 Transformation Config 可以直接复用。最简单的做法是在同一份 Config 中加入 test:
选择 jsdom Environment 时,需要先安装对应 Package:
pnpm add -D jsdom// vite.config.ts
import { fileURLToPath, URL } from "node:url";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
test: {
environment: "jsdom",
},
});如果新增独立的 vitest.config.*,它的优先级更高,Vitest 不会再自动合并原来的 vite.config.*。需要复用原配置时,应使用 mergeConfig 显式合并;如果原配置导出 Config Function,则应在对应 Function 内完成 Merge。Configuring Vitest Vite resolve.alias
日常开发与 CI 怎么用#
| 目标 | 命令 | 说明 |
|---|---|---|
| 本地持续反馈 | pnpm test | Watch Mode 根据 Module Graph 重跑 Related Test |
| 一次性执行 | pnpm test:run | CI 应显式使用 Run Mode,不依赖 Terminal Detection |
| 只跑一个文件 | pnpm exec vitest src/pricing.test.ts | File Argument 按 Path Substring 筛选 |
| 只跑一个 Test | pnpm exec vitest src/pricing.test.ts -t "service errors" | 大仓库同时传 File Path,避免加载无关 Test File |
| 运行受 Source 影响的 Test | pnpm exec vitest related src/pricing.ts --run | 适合 Pre-commit 或增量检查 |
| 生成 Coverage | pnpm test:coverage | Threshold 未达标时返回非零 Exit Code |
| 只运行 Type Test | pnpm exec vitest --typecheck.only | 运行 *.test-d.ts;需要有效的 tsconfig.json |
| Test UI | pnpm exec vitest --ui | 需要额外安装 @vitest/ui |
| CI Shard | pnpm exec vitest run --shard=1/3 | 不可与 Watch Mode 同时使用 |
Watch Mode 的核心收益是利用 Module Graph 找到受改动影响的 Test,而不是每次从头执行整个 Test Suite。随着仓库增大,还可以结合 Filename、-t、Line Number、Related Test 与 Test Tags 过滤。CLI Test Filtering
Vitest 4.1 引入 Test Tags。Tag 必须先在 Config 中定义,再用 --tags-filter 表达式筛选;它适合区分 unit、integration、slow 等稳定 Test Class,但不应取代清晰的文件结构。Test Tags
CI 的最小 Quality Gate 通常包含:
- run: pnpm install --frozen-lockfile
- run: pnpm exec tsc --noEmit
- run: pnpm test:coverage如果项目用 *.test-d.ts 固化公共 Type Contract,再增加 pnpm exec vitest --typecheck.only。其中 tsc --noEmit 检查整个 Project 的类型,Vitest Type Testing 则执行专门的 Type Test;两者关注点不同。
当 Test Suite 增大时,可以增加:
- JUnit / JSON Reporter,供 CI 平台解析 Test Result。
- Test HTML Reporter 或 Vitest UI Artifact,供人工查看 Test Failure;两者需要
@vitest/ui。 - Shard,把 Test File 分散到多个 CI Job,再用 Blob Reporter 合并结果。
test.projects,分别运行 Unit、Integration、DOM 与 Browser Config。
Test HTML Reporter 与前文 coverage.reporter: ["text", "html"] 不是同一份输出:前者报告 Test Result,后者由 Coverage Provider 生成 Coverage HTML Report。
Reporters Vitest UI Test Projects
几类能力应怎样使用#
Mock:隔离不稳定边界,不要伪造整个世界#
vi 可以 Mock Function、Module、Date、Timer、Global、Environment Variable、File System 与 Request。适合隔离第三方 API、Clock、Randomness 和失败重试,但 Mock 只能证明“代码按预期调用了 Test Double”,不能证明双方真实 Protocol 兼容。
关键 HTTP Contract、Database Migration、Queue Message 或 Storage Behavior 应保留少量真实 Integration Test。每个 Test 后应 Clear 或 Restore Mock,避免 State 泄漏到其他 Case。
Coverage:寻找盲区,不把百分比当正确性#
Vitest 支持 V8 与 Istanbul Provider:
- V8 是默认选项,通常执行更快、Memory Overhead 更低;Vitest 3.2 起通过 AST Remapping 提供与 Istanbul 对齐的 Source Coverage 精度。
- Istanbul 在执行前 Instrument Source,适用于非 V8 JavaScript Runtime,但有额外的 Transform 与 Runtime Overhead。
Coverage 应回答“哪些 Branch 与 Error Path 从未执行”,而不是替代 Code Review、Integration Test、Security Test 或需求评审。Vitest 官方还明确说明 V8 Provider 不能用于 Firefox、Bun、Cloudflare Workers 等不暴露 V8 Coverage Profiler 的环境。Coverage
Snapshot:固定稳定 Contract,不盲目按 u#
Snapshot 适合小而稳定、Diff 容易审阅的 Object、Serializer Output、HTML Fragment 或 Generated File。Snapshot File 应提交到 Version Control,并像 Source Code 一样 Review。
如果 Snapshot 很大、包含随机 ID、时间、平台相关路径或频繁变化的文案,开发者容易机械执行 vitest -u,从而把 Regression 一并批准。此时用针对 Behavior 的 Assertion 通常更清晰。Snapshot
Type Testing:验证 Type Contract,不替代 Runtime Test#
Vitest 默认把 *.test-d.ts 识别为 Type Test,并通过 expectTypeOf、assertType 与 TypeScript Compiler 验证公共 API:
// src/pricing.test-d.ts
import { expectTypeOf, test } from "vitest";
import { createQuote } from "./pricing";
test("createQuote keeps its async return contract", () => {
expectTypeOf(createQuote).returns.toEqualTypeOf<Promise<{
sku: string;
quantity: number;
unitPrice: number;
total: number;
}>>();
});Type Test 只做 Static Analysis,不执行 Function Body。--typecheck 会在 Runtime Test 之外启用 Type Test,--typecheck.only 则只运行 Type Test;两者都需要有效的 tsconfig.json。公共 Library 可以同时保留 Runtime Test 与 *.test-d.ts,分别验证 Behavior 和 Type Inference。
截至 Vitest 4.1.11,CLI 仍将内置 Type Checking 标记为 Experimental,并建议 Pin Vitest Version。因此应把它视为补充能力,保留独立的 Project-level Type Check,并在升级 Vitest 时复核行为。Testing Types expectTypeOf API CLI
Parallelism、Projects 与 Sharding:先隔离,再加速#
Vitest 默认并行执行不同 Test File;Vitest 4 的默认 Pool 为 forks。同一 Test File 内默认顺序执行,只有显式使用 test.concurrent 才会并发。Parallelism
这意味着共享 Database、Port、Filesystem Directory 或 Test Account 可能发生冲突。正确顺序是先为每个 Worker / Test 创建独立 Resource,再提高 maxWorkers 或启用更多 Concurrent Test;无法隔离时才使用 fileParallelism: false。
Monorepo 或多环境项目使用 test.projects。旧名称 workspace 自 Vitest 3.2 起已 Deprecated,不应继续复制旧 Config。Test Projects
Cloudflare Workers 的特殊说明#
默认 node Environment 不能证明 Worker Runtime API、Binding、Durable Object 或隔离 Storage 的行为正确。Cloudflare 当前推荐使用官方 @cloudflare/vitest-plugin,让 Vitest Test 在 Workers Runtime 中运行,并提供 Binding Access、每个 Test File 隔离的 Storage、Multi-Worker Project 与本地 Miniflare Execution。当前前置条件包括 Vitest 4.1+、ES Modules Worker,以及 compatibility_date 不早于 2022-10-31。Cloudflare Workers Vitest integration Write your first test
Workers Runtime 不支持 Vitest 的 V8 Coverage Provider;需要 Coverage 时,应安装 @vitest/coverage-istanbul 并把 Provider 配置为 istanbul,不能直接套用前文的 Node.js V8 示例:
pnpm add -D @vitest/coverage-istanbul@4.1.11此外,Vitest Fake Timers 不会推进 KV、R2 与 Cache Simulator 使用的内部时间,因此不能通过调整 Fake Clock 测试这些服务的 TTL 到期。旧的 @cloudflare/vitest-pool-workers 应按官方 Migration Guide 迁移到新 Plugin。Known issues Migrate to the Vitest integration
迁移与配置边界#
- Vitest 与 Jest API Compatible,但不是完全等价。迁移时应重点检查 ESM、Module Mock、Fake Timer、Hook、Snapshot 与 Jest Plugin,不要只替换 Command Name。Migrating from Jest
- Browser Mode 仍处于较早阶段。官方建议关键 Browser Flow 继续由 Playwright、Cypress 或 WebdriverIO 等独立 Runner 补充。Why Browser Mode
- 独立
vitest.config.*不会自动继承vite.config.*。依赖现有 Alias 或 Plugin 时,应放在同一 Config,或显式 Merge。
推荐的落地顺序#
- 从最稳定、最有业务价值的 Pure Function 和 Regression Bug 开始,不先追求 Test 数量。
- 为 Network、Time、Randomness 与 Storage 建立清晰 Boundary,优先 Dependency Injection,再考虑 Module Mock。
- 在 CI 使用
vitest run,同时保留 Type Check;不要让 Watch Mode 留在 CI 中等待。 - 显式配置
coverage.include,记录当前 Baseline,再为关键目录设置可达到的 Threshold。 - 为关键 HTTP、Database、Queue 与 Runtime Adapter 增加少量真实 Integration Test。
- UI Test 先判断是否需要真实 Browser;简单 Behavior 使用 DOM 模拟,Browser-specific Behavior 使用 Browser Mode。
- Test Suite 增长后再引入 Projects、Tags、Reporter 与 Shard;每次并行化前先保证 Resource Isolation。
这样建立起来的测试体系通常会形成三层:
| 层级 | 数量倾向 | 主要目标 | 推荐工具 |
|---|---|---|---|
| Unit / Module | 多 | 快速验证规则、边界与错误 | Vitest node |
| Component / Integration | 适中 | 验证模块协作与 UI Behavior | Vitest + DOM Environment / Browser Mode / Runtime Plugin |
| End-to-End | 少而关键 | 验证完整 User Journey 与部署系统 | Playwright / Cypress 等独立 Runner |
延伸阅读#
- Playwright 使用指南:场景、能力边界与工程实践:系统查看 Browser Automation、Application Flow、Component Testing、Trace、Agent 入口,以及 Playwright 与 Vitest / jsdom 的职责边界。
- 前端 UI 测试全景:Vitest、Playwright、视觉回归与 Agent 契约:从 Component、Layout、Interaction、Visual、E2E 与 Contract Testing 的边界,继续理解 Agent 提交 UI 代码时需要哪些验收证据。
- 浏览器 Fetch receiver 陷阱:为什么网络错误可能发生在请求之前:一个用
vi.stubGlobal固化 Browser API 调用约定的 Regression Test。
官方资料#
- Vitest
- Getting Started
- Why Vitest
- Features
- Configuring Vitest
- CLI
- Test Filtering
- Test Tags
- Mocking
- Coverage
- Snapshot
- Test Environment
- Browser Mode
- Component Testing
- Testing Types
- Test Projects
- Parallelism
- Reporters
- Vitest UI
- Migrating to Vitest 4.0 / from Jest
- Cloudflare Workers Vitest integration
- Cloudflare Workers Vitest integration known issues