单元测试写了 1000 个后,我总结的 5 条铁律
铁律 1:测试行为,不测试实现
TYPESCRIPT
// ❌ 测试实现细节
test('calls setState with correct value', () => {
const spy = jest.spyOn(component, 'setState')
component.handleClick()
expect(spy).toHaveBeenCalledWith({ count: 1 })
})
// ✅ 测试用户可见的行为
test('increments counter when button clicked', () => {
render(<Counter />)
fireEvent.click(screen.getByText('+'))
expect(screen.getByText('1')).toBeInTheDocument()
})铁律 2:一个测试只测一件事
TYPESCRIPT
// ❌ 一个测试测多个行为
test('user login flow', async () => {
// 测试表单验证
// 测试 API 调用
// 测试重定向
// 测试 token 存储
})
// ✅ 拆分独立测试
test('shows error for empty email', async () => { /* ... */ })
test('shows error for invalid email format', async () => { /* ... */ })
test('calls login API with correct payload', async () => { /* ... */ })
test('redirects to dashboard on success', async () => { /* ... */ })铁律 3:AAA 模式
TYPESCRIPT
test('adds item to cart', () => {
// Arrange - 准备数据
const cart = new Cart()
const item = { id: 1, name: 'Book', price: 29.9 }
// Act - 执行操作
cart.add(item)
// Assert - 验证结果
expect(cart.items).toHaveLength(1)
expect(cart.total).toBe(29.9)
})铁律 4:Mock 最少化
TYPESCRIPT
// ❌ 过度 Mock
jest.mock('@/utils/formatDate')
jest.mock('@/utils/validateEmail')
jest.mock('@/hooks/useAuth')
jest.mock('@/services/api')
// ✅ 只 Mock 外部依赖
jest.mock('@/services/api') // 只 mock API 调用
// 纯函数直接测试,不需要 mock铁律 5:测试覆盖边界情况
TYPESCRIPT
describe('divide', () => {
test('divides positive numbers', () => {
expect(divide(10, 2)).toBe(5)
})
test('divides negative numbers', () => {
expect(divide(-10, 2)).toBe(-5)
})
test('throws on division by zero', () => {
expect(() => divide(10, 0)).toThrow('Cannot divide by zero')
})
test('handles decimal results', () => {
expect(divide(1, 3)).toBeCloseTo(0.333, 3)
})
})测试结构模板
TYPESCRIPT
describe('ComponentName', () => {
// 正常路径
describe('when data is loaded', () => {
test('renders items', () => { /* ... */ })
})
// 加载状态
describe('when loading', () => {
test('shows spinner', () => { /* ... */ })
})
// 错误状态
describe('when error occurs', () => {
test('shows error message', () => { /* ... */ })
})
// 空状态
describe('when no data', () => {
test('shows empty state', () => { /* ... */ })
})
})
读者评论 5