Vitest 单元测试和 Mock 策略
✅ Vitest 是专为 Vite 设计的单元测试框架,与 Jest API 兼容。好的 Mock 策略是单元测试有效性的关键。
基础配置
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true, // 免去 import describe/it/expect
environment: 'jsdom', // 或 'node'
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
exclude: ['**/*.d.ts', 'src/test/**']
}
}
})
常用断言
import { describe, it, expect, vi, beforeEach } from 'vitest'
describe('calculateTotal', () => {
it('应正确计算合计', () => {
// Arrange
const items = [{ price: 10 }, { price: 20 }]
// Act
const result = calculateTotal(items)
// Assert
expect(result).toBe(30)
expect(result).toBeGreaterThan(0)
expect(result).toMatchSnapshot()
})
it('应处理空数组', () => {
expect(calculateTotal([])).toBe(0)
})
})
Mock 策略
vi.fn() — 函数 Mock
// 创建模拟函数
const mockFn = vi.fn().mockReturnValue(42)
const asyncMock = vi.fn().mockResolvedValue({ data: 'test' })
// 验证调用
expect(mockFn).toHaveBeenCalledTimes(1)
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2')
vi.mock() — 模块 Mock
// Mock 整个模块
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Test' }),
updateUser: vi.fn().mockResolvedValue({ success: true })
}))
// Mock 部分方法
vi.mock('./utils', async (importOriginal) => {
const original = await importOriginal()
return { ...original, formatDate: vi.fn().mockReturnValue('2024-01-01') }
})
vi.spyOn() — 监听实际实现
const spy = vi.spyOn(console, 'error').mockImplementation(() => {})
// ...do something...
expect(spy).toHaveBeenCalled()
spy.mockRestore()
定时器 Mock
beforeEach(() => {
vi.useFakeTimers()
})
aftrerEach(() => {
vi.useRealTimers()
})
it('应在延迟后触发回调', () => {
const callback = vi.fn()
setTimeout(callback, 1000)
vi.advanceTimersByTime(1000)
expect(callback).toHaveBeenCalled()
})
异步测试
it('应获取用户数据', async () => {
const { data } = await renderHook(() => useUser(1))
await waitFor(() => {
expect(data.current.user).toBeDefined()
})
})
// 测试异步报错
it('应处理网络错误', async () => {
await expect(fetchUser(-1)).rejects.toThrow('User not found')
})
覆盖率要求
// 在 CI 中强制覆盖率
// vitest.config.ts
test: {
coverage: {
thresholds: {
statements: 80,
branches: 70,
functions: 80,
lines: 80
}
}
}
常见误区
- 过度依赖 Mock,测试冠军而实际不能运行
- 每个测试后没有调用 vi.clearAllMocks(),导致测试间状态泄露
- 测试实现细节而非行为,重构后大量测试失败