关于 vitest
vitest 是面向 TypeScript React/Next.js 项目的 Vitest v4 测试技能,服务于编写测试、排查失败和维护测试套件的开发者。内容涵盖测试组织、模拟、异步测试、覆盖率与调试实践。
主要功能
- 01编写单元测试与异步测试
- 02模拟函数、模块和计时器
- 03组织测试及清理状态
- 04排查失败并查看覆盖率
- 05提供 React 与 monorepo 测试参考
适用场景
- 01为 TypeScript 项目添加单元测试
- 02测试 React/Next.js 代码与 DOM 行为
- 03排查模拟失效或测试状态泄漏
- 04评估覆盖率并调试失败用例
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ sablier-labs/ agent-skills --skill vitest安装后在 Claude Code 中输入/vitest确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/monorepo-testing.mdtesting-patterns.mdtroubleshooting.md详细介绍
你的角色
你是擅长为 TypeScript React/Next.js 项目使用 Vitest v4 编写测试的专家。你可以帮助用户编写 高质量测试、调试失败原因,并高效维护测试套件。
典型配置:
- 使用 jsdom 环境的 Vitest v4
- 启用全局 API(
describe、test、expect、vi) - 根据项目配置路径别名
快速开始
运行测试
# Run all unit tests
nlx vitest run
# Run tests matching pattern
nlx vitest run tokens
# Run specific test file
nlx vitest run src/utils/format.test.ts
# Run tests with matching name
nlx vitest run -t "adds token"
# Watch mode
nlx vitest
编写你的第一个测试
文件命名: *.test.ts 或 *.test.tsx
存放位置: 与源文件放在一起
import { describe, test, expect } from "vitest";
import { myFunction } from "./my-function";
describe("myFunction", () => {
test("returns expected value", () => {
expect(myFunction(5)).toBe(10);
});
});
项目特定模式
测试组织
使用视觉分隔符和描述性代码块:
describe("TokenStore", () => {
/* ----------------------------------------------------------------
* Setup
* ------------------------------------------------------------- */
const validToken = { address: "0x123", symbol: "TEST" };
afterEach(() => {
// Reset state between tests
useTokensStore.getState().clearAll();
});
/* ----------------------------------------------------------------
* Adding tokens
* ------------------------------------------------------------- */
describe("addToken", () => {
test("adds valid token and returns true", () => {
const success = useTokensStore.getState().addToken(validToken);
expect(success).toBe(true);
});
});
});
清理模式
始终在 afterEach() 中重置状态:
import { afterEach } from "vitest";
afterEach(() => {
// Reset mocks
vi.clearAllMocks();
// Reset environment
process.env.NODE_ENV = originalEnv;
// Reset stores
});
工厂模拟模式
复杂模拟优先使用工厂函数:
// __mocks__/localStorage.ts
import { vi } from "vitest";
export function createLocalStorageMock() {
const store = new Map<string, string>();
return {
getItem: vi.fn((key: string) => store.get(key) ?? null),
setItem: vi.fn((key: string, value: string) => {
store.set(key, value);
}),
removeItem: vi.fn((key: string) => {
store.delete(key);
}),
clear: vi.fn(() => {
store.clear();
})
};
}
// Usage in tests
import { createLocalStorageMock } from "./__mocks__/localStorage";
const mockStorage = createLocalStorageMock();
global.localStorage = mockStorage as Storage;
共享设置文件
全局模拟和配置放在设置文件中(例如 tests/setup.ts):
import { vi } from "vitest";
// Mock logger for all tests
vi.mock("@/utils/logger", () => ({
createLogger: vi.fn(() => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}))
}));
常见测试场景
测试工具函数
import { describe, test, expect, afterEach } from "vitest";
import { getEnvironment } from "./environment";
describe("getEnvironment", () => {
const originalEnv = process.env.NODE_ENV;
afterEach(() => {
process.env.NODE_ENV = originalEnv;
});
test("returns production when NODE_ENV is production", () => {
process.env.NODE_ENV = "production";
expect(getEnvironment()).toBe("production");
});
test("returns development by default", () => {
process.env.NODE_ENV = undefined;
expect(getEnvironment()).toBe("development");
});
});
异步测试
test("async function resolves correctly", async () => {
const result = await fetchData();
expect(result).toEqual({ data: "value" });
});
test("async function rejects with error", async () => {
await expect(failingFunction()).rejects.toThrow("Error message");
});
模拟函数
import { vi } from "vitest";
// Mock a function
const mockCallback = vi.fn((x: number) => x * 2);
mockCallback(5);
expect(mockCallback).toHaveBeenCalledWith(5);
expect(mockCallback).toHaveReturnedWith(10);
// Spy on object method
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
console.log("test");
expect(spy).toHaveBeenCalledWith("test");
spy.mockRestore();
模拟模块
// At top level, before imports
vi.mock("./api-client", () => ({
fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: "Test" }))
}));
import { fetchUser } from "./api-client";
test("uses mocked API", async () => {
const user = await fetchUser();
expect(user.name).toBe("Test");
});
模拟计时器
import { vi } from "vitest";
test("debounced function", () => {
vi.useFakeTimers();
const callback = vi.fn();
const debounced = debounce(callback, 1000);
debounced();
debounced();
debounced();
vi.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
vi.useRealTimers();
});
调试失败的测试
阅读测试输出
重点关注以下信号:
- 文件和行号 - 失败发生的位置
- 预期值与实际值 - 出了什么问题
- 堆栈跟踪 - 忽略框架内部信息,重点查看自己的代码
常见失败
测试之间状态相互影响:
// Problem: Previous test left state
test("first test", () => {
store.addItem("test");
});
test("second test", () => {
expect(store.items).toHaveLength(0); // Fails! Still has "test"
});
// Solution: Add cleanup
afterEach(() => {
store.clear();
});
模拟不起作用:
// Problem: Mock path doesn't match import
vi.mock("./utils/logger");
import { logger } from "@/utils/logger"; // Different path!
// Solution: Match exact import path
vi.mock("@/utils/logger");
异步超时:
// Problem: Default 5s timeout too short
test("slow operation", async () => {
await verySlowOperation(); // Times out
});
// Solution: Increase timeout
test("slow operation", async () => {
await verySlowOperation();
}, 10000); // 10 second timeout
调试工具
nlx vitest --reporter=verbose # Detailed output
nlx vitest --ui # Visual debugging interface
nlx vitest --coverage # See what's tested
nlx vitest --inspect # Node debugger
nlx vitest --run # Disable watch mode
最佳实践
应该做
- 将测试与源文件放在一起(
feature.ts+feature.test.ts) - 使用
describe代码块分组相关测试 - 使用
afterEach()清理状态和模拟 - 使用视觉分隔符提升可读性(
/* --- */) - 测试行为,而不是实现方式
- 为模拟添加显式类型注解
- 让测试保持聚焦且相互独立
- 修复 bug 前先编写测试(先复现 bug)
不应该做
- 测试实现细节(内部变量)
- 在测试之间共享状态
- 什么都模拟(只模拟边界:网络、存储、时间)
- 忘记恢复模拟和计时器
- 在测试中使用
any类型 - 编写依赖 DOM 结构的脆弱测试
- 为测试工具添加向后兼容补丁
进阶主题
如需深入了解,请参阅 ./references/ 目录:
testing-patterns.md- 完整模式库(组件测试、复杂模拟、异步模式)monorepo-testing.md- 工作区专属策略(共享测试与应用测试、路径别名、组织方式)troubleshooting.md- 调试指南(常见错误、性能、覆盖率、CI/CD)
覆盖率分析
要添加覆盖率:
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["text", "html", "json"],
exclude: ["**/*.test.ts", "**/__mocks__/**", "**/node_modules/**"]
}
}
});
运行:nlx vitest --coverage
配置参考
配置示例:vitest.config.ts
{
environment: "jsdom", // React/DOM APIs available
globals: true, // No imports needed for describe/test/expect
include: ["**/*.test.{js,ts,tsx}"],
exclude: ["**/node_modules/**", "**/e2e/**"],
setupFiles: ["./tests/setup.ts"],
alias: {
"@": "./src",
// Add your project's path aliases
},
}
后续步骤
- 组件测试 - 参阅
./references/testing-patterns.md(React Testing Library 设置) - Monorepo 专属策略 - 参阅
./references/monorepo-testing.md - 调试帮助 - 参阅
./references/troubleshooting.md
从简单的单元测试开始,按需添加组件测试。
评论
支持 Markdown