关于 vitest-testing
vitest-testing 是指导 Vite 项目测试的技能,面向编写或配置 TypeScript 和 JavaScript 单元及集成测试的开发者。内容涵盖 Vitest 配置、覆盖率、模拟,以及识别 jsdom 测试中的无效断言。
主要功能
- 01配置 Vitest 与 Vite
- 02编写 TypeScript 和 JavaScript 测试
- 03模拟、间谍与快照测试
- 04运行覆盖率与基准测试
- 05识别 jsdom 断言陷阱
适用场景
- 01为 Vite 项目配置测试运行器
- 02从 Jest 迁移测试
- 03编写单元或集成测试
- 04检查覆盖率并避免无效 DOM 断言
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ laurigates/ claude-plugins --skill vitest-testing安装后在 Claude Code 中输入/vitest-testing确认已加载。
附带文件REFERENCE.md
详细介绍
Vitest 测试
Vitest 是一款为 Vite 项目设计的现代测试运行器。它速度快、原生支持 ESM,并提供与 Jest 兼容的 API,同时具备更好的 TypeScript 支持和由 HMR 驱动的即时监听模式。
何时使用此技能
| 在以下情况下使用此技能…… | 在以下情况下改用其他技能…… |
|---|---|
| 设置或配置 Vitest | 编写 E2E 浏览器测试(使用 playwright-testing) |
| 编写 TS/JS 单元测试或集成测试 | 测试 Python 代码(使用 python-testing) |
| 从 Jest 迁移到 Vitest | 分析测试质量(使用 test-quality-analysis) |
| 配置覆盖率阈值 | 生成基于属性的测试(使用 property-based-testing) |
| 使用模拟、间谍或假计时器 | 验证测试有效性(使用 mutation-testing) |
核心专长
- 原生支持 Vite:复用 Vite 配置、转换和插件
- 速度快:通过 HMR 驱动的监听模式即时反馈
- 兼容 Jest:可直接替换,API 相似
- TypeScript:一流的 TypeScript 支持
- ESM:原生支持 ESM,无需转译
- jsdom DOM 测试:以及其中会悄悄撒谎的断言(见下文)
安装
bun add --dev vitest
bun add --dev @vitest/coverage-v8 # Coverage (recommended)
bun add --dev happy-dom # DOM testing (optional)
bunx vitest --version # Verify
配置(vitest.config.ts)
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
},
});
常用命令
bunx vitest # Watch mode (default)
bunx vitest run # Run once (CI mode)
bunx vitest --coverage # With coverage
bunx vitest src/utils.test.ts # Specific file
bunx vitest -t "should add numbers" # Filter by name
bunx vitest related src/utils.ts # Related tests
bunx vitest -u # Update snapshots
bunx vitest bench # Benchmarks
bunx vitest --ui # UI mode
编写测试
基本测试结构
import { describe, it, expect } from 'vitest';
import { add, multiply } from './math';
describe('math utils', () => {
it('should add two numbers', () => {
expect(add(2, 3)).toBe(5);
});
it('should multiply two numbers', () => {
expect(multiply(2, 3)).toBe(6);
});
});
常用断言
| 断言 | 说明 |
|---|---|
toBe(value) | 严格相等 |
toEqual(value) | 深度相等 |
toStrictEqual(value) | 深度严格相等 |
toBeTruthy() / toBeFalsy() | 真值判断 |
toBeNull() / toBeUndefined() | Null 检查 |
toBeGreaterThan(n) / toBeLessThan(n) | 数值比较 |
toBeCloseTo(n) | 浮点数比较 |
toMatch(regex) / toContain(str) | 字符串匹配 |
toHaveLength(n) | 数组/字符串长度 |
toHaveProperty(key) | 对象属性 |
toMatchObject(obj) | 对象部分匹配 |
toThrow(msg) | 是否抛出错误 |
异步测试
test('async test', async () => {
const data = await fetchData();
expect(data).toBe('expected');
});
test('promise resolves', async () => {
await expect(fetchData()).resolves.toBe('expected');
});
test('promise rejects', async () => {
await expect(fetchBadData()).rejects.toThrow('error');
});
jsdom 下的 DOM 测试——会撒谎的断言
environment: 'jsdom' 会为你提供一个没有布局引擎的 DOM,其 CSS
解析器也比浏览器的更有限。因此,一些看似自然的断言实际上是空洞的:它们会针对本来要捕捉的错误通过,形成了测试覆盖率,让人不再回头检查。
| 陷阱 | 为什么它会针对错误通过 | 应改为断言 |
|---|---|---|
对样式表设置的样式使用 el.style.overflowY | 内联样式为空;声明位于类规则中 | getComputedStyle(el).overflowY——jsdom 确实会解析注入的 <style> 规则 |
对 min() / calc() 值使用 getComputedStyle(el).width | jsdom 的解析器会悄悄丢弃整个声明,无论如何都会报告 ""/0 | 样式表的源文本,或交由真实浏览器处理 |
| 任何有关尺寸或位置的断言 | getBoundingClientRect() 的值全为零;这里没有布局 | 真实浏览器测试层 |
| 点击后立即断言异步渲染的内容 | 面板仍为空,因此“未找到不良元素”自然成立 | 刷新(await new Promise(r => setTimeout(r, 0))),然后在真正检查前断言容器非空 |
另外:jsdom 中不存在 Element.prototype.scrollIntoView,因此任何在挂载时将元素居中的代码路径都会抛出错误。请为它添加桩(Element.prototype.scrollIntoView = () => {})——这是测试框架的缺口,不是行为变更。
并且绝不要编写条件断言:
// Passes silently in exactly the case it was meant to catch — a renamed class.
if (found.length === 1) expect(found[0].textContent).toMatch(/x/);
// Assert unconditionally.
expect(found).toHaveLength(1);
expect(found[0].textContent).toMatch(/x/);
故意让测试失败
一个从未失败过的回归测试,尚未证明自己能测试任何内容。信任它之前,先强制让它变红,并阅读报错信息:
- 在此软件包中修复 → 原地撤销该修复,确认测试因正确原因变红,然后恢复修复。
- 跨软件包测试套件 → 将依赖重新固定到修复前的版本,确认测试变红,然后恢复固定版本。
在 PR 正文中记录观察到的失败输出。“它会变红”只是说法;报错信息才是证据。
加载同级软件包的源码进行真实集成测试
当一个缺陷是两个软件包共同作用的结果时,分别使用替代品测试会让它们都通过,但组合在一起时却会出错。要加载同级软件包的真实源码:
- 将其依赖固定到一个发布标签(例如 Git 依赖),这样测试套件测试的是已发布的制品,而不是不断变化的分支;
- 将其添加到
server.deps.inline中——Vitest 默认会将node_modules外部化,并让 Node 直接处理原始 TypeScript:
export default defineConfig({
test: { server: { deps: { inline: [/sibling-package/] } } },
});
- 导出宿主的真实入口点,让测试套件运行实际的代码路径,而不是可能无法复现该缺陷的单元测试接缝。
模拟(基本模式)
import { vi, test, expect } from 'vitest';
// Mock function
const mockFn = vi.fn();
mockFn.mockReturnValue(42);
// Mock module
vi.mock('./api', () => ({
fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: 'John' })),
}));
// Mock timers
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
vi.restoreAllMocks();
// Spy on method
const spy = vi.spyOn(object, 'method');
快照测试
test('snapshot test', () => {
expect(data).toMatchSnapshot();
});
test('inline snapshot', () => {
expect(result).toMatchInlineSnapshot('5');
});
// Update snapshots: bunx vitest -u
覆盖率
bun add --dev @vitest/coverage-v8
bunx vitest --coverage
关键配置选项:provider、reporter、include、exclude、thresholds。
Agent 优化
| 场景 | 命令 |
|---|---|
| 快速测试 | bunx vitest --reporter=dot --bail=1 |
| CI 测试 | bunx vitest run --reporter=junit |
| 覆盖率检查 | bunx vitest --coverage --reporter=dot |
| 单个文件 | bunx vitest run src/utils.test.ts --reporter=dot |
| 仅运行失败项 | bunx vitest --changed --bail=1 |
如需详细示例、高级模式和最佳实践,请参阅 REFERENCE.md。