vitest
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseYour Role
你的角色
You are an expert in writing tests with Vitest v4 for TypeScript React/Next.js projects. You help users write
high-quality tests, debug failures, and maintain test suites efficiently.
Typical setup:
- Vitest v4 with jsdom environment
- Globals enabled (,
describe,test,expect)vi - Path aliases configured per project
Skill delegation:
If the task is related to Zustand store testing, activate theskillzustand
你是一位在TypeScript React/Next.js项目中使用Vitest v4编写测试的专家。你帮助用户编写高质量的测试、调试测试失败问题,并高效维护测试套件。
典型配置:
- 搭配jsdom环境的Vitest v4
- 启用全局变量(,
describe,test,expect)vi - 按项目配置路径别名
技能委托:
如果任务与Zustand store测试相关,请激活技能zustand
Quick Start
快速开始
Running Tests
运行测试
bash
undefinedbash
undefinedRun all unit tests
运行所有单元测试
nlx vitest run
nlx vitest run
Run tests matching pattern
运行匹配指定模式的测试
nlx vitest run tokens
nlx vitest run tokens
Run specific test file
运行指定测试文件
nlx vitest run src/utils/format.test.ts
nlx vitest run src/utils/format.test.ts
Run tests with matching name
运行匹配指定名称的测试
nlx vitest run -t "adds token"
nlx vitest run -t "adds token"
Watch mode
监听模式
nlx vitest
undefinednlx vitest
undefinedWriting Your First Test
编写你的第一个测试
File naming: or
*.test.ts*.test.tsxLocation: Colocate with source files
typescript
import { describe, test, expect } from "vitest";
import { myFunction } from "./my-function";
describe("myFunction", () => {
test("returns expected value", () => {
expect(myFunction(5)).toBe(10);
});
});文件命名规则: 或
*.test.ts*.test.tsx存放位置: 与源文件放在同一目录下
typescript
import { describe, test, expect } from "vitest";
import { myFunction } from "./my-function";
describe("myFunction", () => {
test("返回预期值", () => {
expect(myFunction(5)).toBe(10);
});
});Project-Specific Patterns
项目特定模式
Test Organization
测试组织
Use visual separators and descriptive blocks:
typescript
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);
});
});
});使用视觉分隔符和描述性代码块:
typescript
describe("TokenStore", () => {
/* ----------------------------------------------------------------
* 初始化设置
* ------------------------------------------------------------- */
const validToken = { address: "0x123", symbol: "TEST" };
afterEach(() => {
// 在测试之间重置状态
useTokensStore.getState().clearAll();
});
/* ----------------------------------------------------------------
* 添加代币
* ------------------------------------------------------------- */
describe("addToken", () => {
test("添加有效代币并返回true", () => {
const success = useTokensStore.getState().addToken(validToken);
expect(success).toBe(true);
});
});
});Cleanup Pattern
清理模式
Always reset state in :
afterEach()typescript
import { afterEach } from "vitest";
afterEach(() => {
// Reset mocks
vi.clearAllMocks();
// Reset environment
process.env.NODE_ENV = originalEnv;
// Reset stores (if not using zustand skill)
// For Zustand stores, use the `zustand` skill
});务必在中重置状态:
afterEach()typescript
import { afterEach } from "vitest";
afterEach(() => {
// 重置模拟函数
vi.clearAllMocks();
// 重置环境变量
process.env.NODE_ENV = originalEnv;
// 重置store(如果未使用zustand技能)
// 对于Zustand store,请使用`zustand`技能
});Factory Mock Pattern
工厂模拟模式
Prefer factory functions for complex mocks:
typescript
// __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;对于复杂的模拟,优先使用工厂函数:
typescript
// __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();
})
};
}
// 在测试中使用
import { createLocalStorageMock } from "./__mocks__/localStorage";
const mockStorage = createLocalStorageMock();
global.localStorage = mockStorage as Storage;Shared Setup File
共享初始化文件
Global mocks and configuration live in a setup file (e.g., ):
tests/setup.tstypescript
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()
}))
}));全局模拟和配置存放在初始化文件中(例如):
tests/setup.tstypescript
import { vi } from "vitest";
// 为所有测试模拟日志工具
vi.mock("@/utils/logger", () => ({
createLogger: vi.fn(() => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}))
}));Common Testing Scenarios
常见测试场景
Testing Utilities
测试工具函数
typescript
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");
});
});typescript
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("当NODE_ENV为production时返回production", () => {
process.env.NODE_ENV = "production";
expect(getEnvironment()).toBe("production");
});
test("默认返回development", () => {
process.env.NODE_ENV = undefined;
expect(getEnvironment()).toBe("development");
});
});Async Testing
异步测试
typescript
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");
});typescript
test("异步函数正确解析", async () => {
const result = await fetchData();
expect(result).toEqual({ data: "value" });
});
test("异步函数抛出错误", async () => {
await expect(failingFunction()).rejects.toThrow("Error message");
});Mocking Functions
模拟函数
typescript
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();typescript
import { vi } from "vitest";
// 模拟一个函数
const mockCallback = vi.fn((x: number) => x * 2);
mockCallback(5);
expect(mockCallback).toHaveBeenCalledWith(5);
expect(mockCallback).toHaveReturnedWith(10);
// 监听对象方法
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
console.log("test");
expect(spy).toHaveBeenCalledWith("test");
spy.mockRestore();Mocking Modules
模拟模块
typescript
// 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");
});typescript
// 在顶层,导入之前
vi.mock("./api-client", () => ({
fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: "Test" }))
}));
import { fetchUser } from "./api-client";
test("使用模拟的API", async () => {
const user = await fetchUser();
expect(user.name).toBe("Test");
});Timer Mocking
定时器模拟
typescript
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();
});typescript
import { vi } from "vitest";
test("防抖函数", () => {
vi.useFakeTimers();
const callback = vi.fn();
const debounced = debounce(callback, 1000);
debounced();
debounced();
debounced();
vi.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
vi.useRealTimers();
});Debugging Failed Tests
调试失败的测试
Reading Test Output
读取测试输出
Focus on these signals:
- File and line number - Where the failure occurred
- Expected vs. received - What went wrong
- Stack trace - Ignore framework internals, focus on your code
重点关注以下信息:
- 文件和行号 - 失败发生的位置
- 预期值与实际值 - 问题所在
- 堆栈跟踪 - 忽略框架内部代码,聚焦你自己的代码
Common Failures
常见失败原因
State bleeding between tests:
typescript
// 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();
});Mock not working:
typescript
// 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");Async timeout:
typescript
// 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测试间状态泄露:
typescript
// 问题:上一个测试留下了状态
test("第一个测试", () => {
store.addItem("test");
});
test("第二个测试", () => {
expect(store.items).toHaveLength(0); // 失败!仍然存在"test"
});
// 解决方案:添加清理步骤
afterEach(() => {
store.clear();
});模拟未生效:
typescript
// 问题:模拟路径与导入路径不匹配
vi.mock("./utils/logger");
import { logger } from "@/utils/logger"; // 路径不同!
// 解决方案:匹配完全一致的导入路径
vi.mock("@/utils/logger");异步超时:
typescript
// 问题:默认5秒超时时间过短
test("慢操作", async () => {
await verySlowOperation(); // 超时
});
// 解决方案:增加超时时间
test("慢操作", async () => {
await verySlowOperation();
}, 10000); // 10秒超时Debugging Tools
调试工具
bash
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 modebash
nlx vitest --reporter=verbose # 详细输出
nlx vitest --ui # 可视化调试界面
nlx vitest --coverage # 查看测试覆盖率
nlx vitest --inspect # Node调试器
nlx vitest --run # 禁用监听模式Best Practices
最佳实践
DO
建议
- Colocate tests with source files (+
feature.ts)feature.test.ts - Use blocks to group related tests
describe - Add cleanup for state/mocks
afterEach() - Use visual separators for clarity ()
/* --- */ - Test behavior, not implementation
- Use explicit type annotations for mocks
- Keep tests focused and independent
- Write tests before fixing bugs (reproduce the bug first)
- 将测试与源文件放在同一目录下(+
feature.ts)feature.test.ts - 使用块对相关测试进行分组
describe - 为状态/模拟添加清理步骤
afterEach() - 使用视觉分隔符提高可读性()
/* --- */ - 测试行为而非实现细节
- 为模拟函数添加明确的类型注解
- 保持测试聚焦且相互独立
- 在修复bug前先编写测试(先复现bug)
DON'T
不建议
- Test implementation details (internal variables)
- Share state between tests
- Mock everything (only mock boundaries: network, storage, time)
- Forget to restore mocks/timers
- Use types in tests
any - Create brittle tests tied to DOM structure
- Add backward-compatibility hacks for test utilities
- 测试实现细节(内部变量)
- 在测试间共享状态
- 模拟所有内容(仅模拟边界:网络、存储、时间)
- 忘记恢复模拟/定时器
- 在测试中使用类型
any - 创建依赖DOM结构的脆弱测试
- 为测试工具添加向后兼容的hack代码
Advanced Topics
高级主题
For deeper dives, see the directory:
./references/- - Complete pattern library (component tests, complex mocking, async patterns)
TESTING_PATTERNS.md - - Workspace-specific strategies (shared vs. app tests, path aliases, organization)
MONOREPO_TESTING.md - - Debug guide (common errors, performance, coverage, CI/CD)
TROUBLESHOOTING.md
如需深入了解,请查看目录:
./references/- - 完整的模式库(组件测试、复杂模拟、异步模式)
TESTING_PATTERNS.md - - 工作区特定策略(共享测试与应用测试、路径别名、组织方式)
MONOREPO_TESTING.md - - 调试指南(常见错误、性能、覆盖率、CI/CD)
TROUBLESHOOTING.md
Coverage Analysis
覆盖率分析
To add coverage:
typescript
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["text", "html", "json"],
exclude: ["**/*.test.ts", "**/__mocks__/**", "**/node_modules/**"]
}
}
});Run with:
nlx vitest --coverage添加覆盖率配置:
typescript
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["text", "html", "json"],
exclude: ["**/*.test.ts", "**/__mocks__/**", "**/node_modules/**"]
}
}
});运行命令:
nlx vitest --coverageConfiguration Reference
配置参考
Example config:
vitest.config.tstypescript
{
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
},
}示例配置:
vitest.config.tstypescript
{
environment: "jsdom", // 支持React/DOM API
globals: true, // 无需导入describe/test/expect
include: ["**/*.test.{js,ts,tsx}"],
exclude: ["**/node_modules/**", "**/e2e/**"],
setupFiles: ["./tests/setup.ts"],
alias: {
"@": "./src",
// 添加你的项目路径别名
},
}Next Steps
后续步骤
- For Zustand store testing - Activate skill
zustand - For component testing - See (React Testing Library setup)
./references/TESTING_PATTERNS.md - For monorepo-specific strategies - See
./references/MONOREPO_TESTING.md - For debugging help - See
./references/TROUBLESHOOTING.md
Start with simple unit tests, add component tests as needed.
- 如需测试Zustand store - 激活技能
zustand - 如需组件测试 - 查看(React Testing Library配置)
./references/TESTING_PATTERNS.md - 如需单仓库多项目策略 - 查看
./references/MONOREPO_TESTING.md - 如需调试帮助 - 查看
./references/TROUBLESHOOTING.md
从简单的单元测试开始,根据需要添加组件测试。