关于 vitest-v4
面向编写、配置和调试测试套件的开发者的 Vitest 4 技能,涵盖 Vite 原生实践、测试执行、模拟、覆盖率、浏览器测试和多项目配置。
主要功能
- 01Vitest 4 设置指导
- 02模拟与清理模式
- 03覆盖率配置
- 04Browser Mode 测试
- 05多项目配置
适用场景
- 01在 CI 中运行非监视模式测试
- 02修复测试间模拟对象泄漏
- 03为未测试文件配置覆盖率
- 04排查 Browser Mode 间谍问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nodnarbnitram/ claude-code-extensions --skill vitest-v4安装后在 Claude Code 中输入/vitest-v4确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdreferences/browser-mode-reference.mdcoverage-projects-reference.mdmocking-reference.mdpools-execution-reference.mdREADME.md详细介绍
Vitest 4 测试技能
使用 Vite 原生模式编写、配置和调试 Vitest 4 测试套件。
开始之前
此技能可避免 7 种以上常见的 Vitest 4 错误,并节省约 50% 的令牌。
| 指标 | 不使用技能 | 使用技能 |
|---|---|---|
| 设置时间 | 约 90 分钟 | 约 30 分钟 |
| 常见错误 | 7 种以上 | 0 |
| 令牌用量 | 高(反复试错) | 低(采用已知模式) |
此技能可避免的已知问题
- 使用监听模式而非
vitest run,导致 Agent 运行卡住 - 使用已移除的
coverage.all或coverage.extensions,导致覆盖率配置失效 - 浏览器模式下因 ESM 命名空间对象已封闭,导致间谍功能失败
- 缺少还原/重置配置,导致 Mock 在测试间泄漏
- 使用已弃用的
workspace术语,导致多项目配置无效 - 在 Vitest 测试中混用 Jest 助手,导致 API 错误
- 使用合成助手而非
vitest/browser,导致浏览器交互不稳定 - 为大型测试套件选择了错误的执行池或隔离模式,导致运行缓慢或不稳定
快速开始
步骤 1:配置 Vitest 4,确保 Agent 安全运行
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node',
restoreMocks: true,
clearMocks: true,
coverage: {
provider: 'v8',
include: ['src/**/*.{ts,tsx}'],
},
},
});
原因: Vitest 4 移除了 coverage.all 和 coverage.extensions;Agent/CI 环境需要一次性执行并自动清理 Mock。
步骤 2:使用 Vitest API 编写测试,而非 Jest API
import { describe, expect, it, vi } from 'vitest';
import { addUser } from './add-user';
import * as api from './api';
describe('addUser', () => {
it('returns the created user', async () => {
vi.spyOn(api, 'createUser').mockResolvedValue({ id: '1', name: 'Ada' });
await expect(addUser('Ada')).resolves.toEqual({ id: '1', name: 'Ada' });
});
});
原因: vi 是受支持的 Mock API。混用 jest.fn() 或仅适用于 Jest 的模式会导致令人困惑的失败,并造成自动补全效果不佳。
导入规则: 除非项目明确启用了
globals: true,否则请从vitest导入describe、it、expect和vi。
步骤 3:使用正确的运行时命令
vitest run
vitest run --coverage
vitest run path/to/example.test.ts
原因: 在开发环境中,不带 run 的 vitest 默认会启动监听模式,不适合 Agent、CI 和非交互式验证。
关键规则
始终遵守
- 在 Agent 和 CI 工作流中使用
vitest run或vitest --no-watch - 优先使用
vi.mock(import('./module'))创建类型安全的模块 Mock - 有意配置
restoreMocks、clearMocks或mockReset - 多项目配置使用
projects;此重命名始于 Vitest 3.2,旧版的 workspace 文件用法已在 Vitest 4 中移除 - 多个
projects需要共享设置时,使用共享基础配置;除非选择加入,否则项目不会继承根配置 - 使用
coverage.include报告未经测试的源文件 - 在浏览器模式下使用
vitest/browser中的page和userEvent - 原生模块或运行时兼容性比原始速度更重要时,优先使用
forks - 请求 AI 生成测试时,提供
vitest.config.ts和实现文件
绝不遵守
- 绝不在 Vitest 代码中使用
jest.fn、jest.spyOn或仅适用于 Jest 的全局变量 - 绝不在 Vitest 4 中依赖已移除的
coverage.all或coverage.extensions - 绝不使用普通监听模式进行 Agent 驱动的验证
- 绝不在浏览器模式下对原生 ESM 导出使用
vi.spyOn - 绝不忘记
vi.mock()会在文件其余部分执行前提升 - 绝不让环境变量/全局对象 Stub 在测试间残留
常见错误
错误——使用已移除的覆盖率选项:
export default defineConfig({
test: {
coverage: {
all: true,
},
},
});
正确——使用 include glob:
export default defineConfig({
test: {
coverage: {
provider: 'v8',
include: ['src/**/*.{ts,tsx}'],
},
},
});
原因: Vitest 4 移除了 coverage.all 和 coverage.extensions;coverage.include 是纳入未覆盖文件的受支持方式。
错误——在浏览器模式下对已封闭的导出使用间谍:
import * as math from './math';
import { vi } from 'vitest';
vi.spyOn(math, 'add').mockReturnValue(10);
正确——使用启用间谍的模块 Mock:
import { vi } from 'vitest';
vi.mock(import('./math'), { spy: true });
原因: 原生浏览器 ESM 命名空间对象已封闭,因此在浏览器模式下直接对导出使用间谍会失败。
已知问题的预防
| 问题 | 根本原因 | 解决方案 |
|---|---|---|
| 测试始终无法退出 | 在非交互式会话中启动了监视模式 | 使用 vitest run |
| 覆盖率报告遗漏未测试的文件 | 未配置 coverage.include | 添加明确的源文件 glob 模式 |
| Browser Mode spy 报错或不起作用 | 在已封存的 ESM 导出上使用了 vi.spyOn | 使用 vi.mock(import('./mod'), { spy: true }) |
| 模拟在测试之间泄漏 | 缺少清理标志 | 启用 restoreMocks / clearMocks / unstubEnvs |
| 升级后多项目配置失效 | 沿用了已弃用的 workspace 术语或已移除的 workspace 文件模式 | 改用 projects 和 defineProject |
| Worker 或池配置停止工作 | 沿用了旧的 maxThreads、maxForks 或 poolOptions | 迁移到 Vitest 4 的 Worker 设置,例如 maxWorkers |
| 项目专属配置意外消失 | 误以为根配置中的设置会被 projects 继承 | 显式使用 extends: true、mergeConfig 或共享基础配置 |
| AI 生成的测试使用了错误的辅助工具 | 将 Jest 模式照搬到了 Vitest | 改用 vi、Vitest 导入项和 Vitest 匹配器 |
| 浏览器测试挂起 | 阻塞式对话框或错误的 user-event 工具 | 模拟对话框并使用 vitest/browser 辅助工具 |
| 快速池导致奇怪的原生模块故障 | 为需要进程隔离的测试套件选择了 threads | 改用 forks 或缩小线程的使用范围 |
捆绑资源
参考资料
- 模拟规则与提升 →
references/mocking-reference.md - Browser Mode 提供程序与注意事项 →
references/browser-mode-reference.md - 覆盖率与多项目配置 →
references/coverage-projects-reference.md - 池、隔离与持久化缓存 →
references/pools-execution-reference.md - 参考资料索引 →
references/README.md
配置参考
vitest.config.ts
import { defineConfig, defineProject } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';
export default defineConfig({
test: {
projects: [
defineProject({
test: {
name: 'unit',
include: ['src/**/*.test.ts'],
environment: 'node',
},
}),
defineProject({
test: {
name: 'browser',
include: ['src/**/*.browser.test.ts'],
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
},
},
}),
],
coverage: {
provider: 'v8',
include: ['src/**/*.{ts,tsx}'],
},
restoreMocks: true,
unstubEnvs: true,
setupFiles: ['./test/setup.ts'],
},
});
关键设置:
test.projects:稳定的多项目术语;更名始于 Vitest 3.2,并且项目不会自动继承所有根配置值,因此需要时应将共享设置提取到可重复使用的基础配置中coverage.include:需要在报告中显示未覆盖的源文件时必须设置browser.provider:在 Vitest 4 中,从提供程序包导入提供程序工厂,例如playwright()restoreMocks/unstubEnvs:防止测试之间相互污染setupFiles:在测试文件运行前执行共享测试初始化,例如 MSW、全局变量或 polyfill
项目结构
my-app/
├── src/
│ ├── feature.ts
│ ├── feature.test.ts
│ └── feature.browser.test.ts
├── vitest.config.ts
├── vite.config.ts
└── package.json
重要性: 清晰分隔 Node 和 Browser Mode 测试,可以大幅简化提供程序设置、测试选择和故障排查。
选择合适的环境: 大多数组件测试和轻量级 DOM 断言优先使用 jsdom。当需要原生浏览器 API、真实的布局或事件行为,或截图断言时,使用 Browser Mode。
选择合适的执行模型: 为了稳定性和原生模块兼容性,尤其是在混合型或基础设施密集型测试套件中,优先使用 forks。只有在确认测试环境适合 Worker 线程执行,且额外速度确有价值时,才使用 threads。
常见模式
类型安全的模块模拟模式
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getUserName } from './get-user-name';
import * as api from './api';
vi.mock(import('./api'), () => ({
fetchUser: vi.fn(),
}));
describe('getUserName', () => {
beforeEach(() => {
vi.mocked(api.fetchUser).mockReset();
});
it('returns the fetched user name', async () => {
vi.mocked(api.fetchUser).mockResolvedValue({ id: '1', name: 'Ada' });
await expect(getUserName('1')).resolves.toBe('Ada');
});
});
Browser Mode 交互模式
import { expect, test } from 'vitest';
import { page, userEvent } from 'vitest/browser';
import { render } from 'vitest-browser-react';
import { Counter } from './counter';
test('increments after click', async () => {
render(<Counter />);
await userEvent.click(page.getByRole('button', { name: /increment/i }));
await expect.element(page.getByText('1')).toBeInTheDocument();
});
源码内测试模式
export function sum(a: number, b: number) {
return a + b;
}
if (import.meta.vitest) {
const { it, expect } = import.meta.vitest;
it('adds numbers', () => {
expect(sum(1, 2)).toBe(3);
});
}
依赖项
必需
| Package | Version | Purpose |
|---|---|---|
vitest | ^4 | 测试运行器以及断言/模拟 API |
vite | ^6 | 由 Vite 驱动的共享模块流水线 |
node | >=20 | Vitest 4 所需的运行时 |
可选
| 软件包 | 版本 | 用途 |
|---|---|---|
@vitest/coverage-v8 | ^4 | 通过 AST 重映射实现快速、准确的覆盖率统计 |
@vitest/coverage-istanbul | ^4 | Istanbul 覆盖率后端 |
@vitest/browser-playwright | ^4 | Browser Mode 的 Playwright 提供程序 |
@vitest/browser-webdriverio | ^4 | Browser Mode 的 WebdriverIO 提供程序 |
@vitest/browser-preview | ^4 | Browser Mode 的预览提供程序 |
官方文档
故障排除
Agent 运行一直卡住
症状: 测试进程始终不退出,或 Claude 一直等待文件变更。
解决方案:
vitest run
Browser Mode 测试无法监视导出项
症状: vi.spyOn() 抛出错误、没有效果,或在 Node 模式下正常工作但在浏览器中失败。
解决方案:
vi.mock(import('./module'), { spy: true });
覆盖率统计未包含没有测试的源文件
症状: 报告仅包含已执行测试涉及的文件。
解决方案:
coverage: {
provider: 'v8',
include: ['src/**/*.{ts,tsx}'],
}
升级后旧版 worker 或池设置失效
症状: 从 Vitest 4 升级后,旧的 maxThreads、maxForks、singleThread、singleFork 或 poolOptions 设置停止工作。
解决方案:
test: {
maxWorkers: 4,
}
原因: Vitest 4 简化了 worker 配置,并移除了若干旧版特定于池的选项。
设置检查清单
使用此技能前,请确认:
- Node.js 为
>=20 -
vite为>=6 -
vitest.config.ts或vite.config.ts包含test块 - Agent/CI 命令使用
vitest run - 启用覆盖率时已安装覆盖率提供程序软件包
- 启用 Browser Mode 时已安装浏览器提供程序软件包