关于 vitest-testing
面向使用 Vitest 的开发者,提供单元、组件、集成和 Hook 测试的实用指南。涵盖常用 API、测试库模式、配置与 CLI 操作。
主要功能
- 01测试结构、断言与生命周期钩子
- 02函数、模块和方法模拟
- 03React 组件与 Hook 测试
- 04异步断言与快照
- 05CLI 筛选、并发与分片
适用场景
- 01为 Vite 项目编写单元测试
- 02用 React Testing Library 验证组件交互
- 03测试 TanStack Query、Router 或 Form 模式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ oakoss/ agent-skills --skill vitest-testing安装后在 Claude Code 中输入/vitest-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/advanced-patterns.mdcli-and-filtering.mdcomponent-testing.mdconfiguration.mdfixtures-and-context.mdhook-testing.mdmocking.mdtest-fundamentals.mdtest-setup.md详细介绍
Vitest 测试
概述
Vitest 是一个原生支持 Vite 的单元测试框架,与 Vite 共用配置和插件生态系统。它原生支持 ESM、支持测试热模块替换和并行执行,旨在实现快速运行。
适用场景: 单元测试、组件测试、集成测试、Hook 测试、源码内测试。使用 React Testing Library 测试 React 组件。测试 TanStack Query/Router/Form 模式。
不适用场景: 端到端测试(使用 Playwright)、视觉回归测试(使用 Percy/Chromatic)、负载测试(使用 k6)。
快速参考
| 模式 | API | 关键要点 |
|---|---|---|
| 测试结构 | describe('suite', () => {}) | 组织相关测试 |
| 单个测试 | test('behavior', () => {}) or it() | 两者是别名 |
| 参数化测试 | test.each([...])('name', (arg) => {}) | 使用不同输入运行同一测试 |
| 并发测试 | test.concurrent('name', async () => {}) | 并行运行测试 |
| 跳过测试 | test.skip('name', () => {}) or skipIf(cond) | 有条件地跳过测试 |
| 仅运行此测试 | test.only('name', () => {}) | 运行特定测试以进行调试 |
| 断言 | expect(value).toBe(expected) | 比较基本类型值 |
| 深度相等 | expect(obj).toEqual(expected) | 比较对象/数组 |
| 异步断言 | await expect(promise).resolves.toBe(value) | 测试 Promise 是否成功兑现 |
| 每个测试前 | beforeEach(() => {}) | 在每个测试前进行设置 |
| 每个测试后 | afterEach(() => {}) | 在每个测试后进行清理 |
| 模拟函数 | vi.fn() | 创建间谍函数 |
| 模拟模块 | vi.mock('./module') | 替换整个模块 |
| 监视方法 | vi.spyOn(obj, 'method') | 跟踪对现有方法的调用 |
| 清除模拟 | vi.clearAllMocks() | 清除调用历史 |
| 虚假计时器 | vi.useFakeTimers() | 控制 setTimeout/setInterval |
| 渲染组件 | render(<Component />) | 挂载 React 组件以进行测试 |
| 按角色查询 | screen.getByRole('button') | 按无障碍角色查找元素 |
| 用户交互 | await user.click(element) | 模拟用户事件 |
| 等待变化 | await waitFor(() => expect(...)) | 等待异步变化 |
| 查找异步元素 | await screen.findByText('text') | 合并查询与等待 |
| 渲染 Hook | renderHook(() => useHook()) | 测试自定义 Hook |
| 更新 Hook 属性 | rerender(newProps) | 使用新属性重新渲染 Hook |
| 快照 | expect(value).toMatchSnapshot() | 与已保存的快照进行比较 |
| 内联快照 | expect(value).toMatchInlineSnapshot() | 将快照存储在测试文件中 |
| CLI 运行一次 | vitest run | 单次运行,不启用监视 |
| 运行已更改的测试 | vitest --changed | 运行受 git 变更影响的测试 |
| 按名称筛选 | vitest -t "pattern" | 搜索测试名称 |
| 软断言 | expect.soft(value).toBe(x) | 失败时继续执行并收集所有结果 |
| 轮询断言 | await expect.poll(() => val).toBe(x) | 重试直到通过 |
| 测试夹具 | const test = base.extend<F>({...}) | 通过 test.extend 复用设置 |
| 提升的模拟 | vi.hoisted(() => ({ fn: vi.fn() })) | 为 vi.mock 工厂提供变量 |
| 分片测试 | vitest --shard 1/3 | 在 CI 工作器之间拆分 |
| 标签 | test('name', { tags: ['slow'] }, ...) | 使用 --tags-filter 筛选 |
| 替换全局对象 | vi.stubGlobal('fetch', vi.fn()) | 干净地替换全局对象 |
常见错误
| 错误 | 正确做法 |
|---|---|
对异步内容使用 getBy | 对异步内容使用 findBy 或 waitFor |
| 测试实现细节 | 测试行为和公共 API |
首选 getByTestId | 优先使用 getByRole、getByLabelText、getByText |
忘记调用 userEvent.setup() | 始终先调用 const user = userEvent.setup() |
| 在测试间共享状态 | 使用 beforeEach 重置状态或创建新实例 |
| 未清理模拟 | 在 afterEach 中使用 vi.clearAllMocks() |
| 模拟过多 | 只模拟外部依赖和 API |
| 未在测试中禁用重试 | 在 TanStack Query 测试中设置 retry: false |
| 对异步操作立即进行断言 | 使用 await waitFor() 或 findBy 查询 |
| 在 render 中创建 QueryClient | 在包装器中创建一次,或使用 useState |
| 测试库代码 | 信任库,测试你的使用方式 |
| 未等待用户事件 | 所有 user.* 方法都会返回 Promise |
手动使用 act | userEvent 和 Testing Library 会处理此事 |
| 内联 select 未进行记忆化 | 提取为稳定函数,以供 TanStack Query 使用 |
| 在 vi.mock 工厂中使用变量 | 使用 vi.hoisted() 声明模拟变量 |
使用单个 expect 进行多项检查 | 使用 expect.soft() 收集所有失败 |
在测试中使用 setTimeout 等待异步操作 | 使用 expect.poll() 或 vi.waitFor() |
为可复用设置手动编写 beforeEach | 使用 test.extend fixture 实现可组合设置 |
委派
- 测试发现:查找未覆盖的代码路径时,使用
ExploreAgent - 覆盖率分析:进行完整覆盖率审查时,使用
TaskAgent - E2E 测试:如果有
playwright技能,将 E2E 测试委派给它 - 代码审查:编写测试后,委派给
code-reviewerAgent
参考资料
- 测试基础:结构、断言、生命周期钩子
- 模拟:vi.fn、vi.mock、vi.spyOn、模块模拟
- 组件测试:React Testing Library、查询、user-event
- Hook 测试:renderHook、异步 Hook、TanStack 模式
- 测试设置:jest-dom 匹配器、清理、自定义 render、MSW、polyfill
- 配置:vitest.config.ts、workspace、覆盖率、报告器
- 高级模式:快照、并发测试、源码内测试、类型测试
- CLI 和筛选:命令、监视模式、标签、分片
- Fixture 和上下文:test.extend、作用域、自动 Fixture、注入
评论
支持 Markdown