跳到主要内容

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(),导致测试间状态泄露
  • 测试实现细节而非行为,重构后大量测试失败