Mock和Stub指南
测试替身类型
| 类型 | 定义 | 验证调用? | 返回值? | 使用场景 |
|---|---|---|---|---|
| Dummy | 传入但从不使用 | 否 | 否 | 填充必需参数 |
| Stub | 返回预设固定值 | 否 | 是(固定) | 控制间接输入 |
| Fake | 可工作的简化实现 | 否 | 是(真实逻辑) | 内存数据库、模拟邮件发送 |
| Spy | 记录调用,事后断言 | 是(事后) | 可选 | 验证副作用,无需严格预设 |
| Mock | 预先设置期望,严格验证 | 是(严格) | 是(配置的) | 预先验证精确交互 |
Jest:Mock vs Spy
beforeEach(() => {
// MOCK:替换实现,自动 mock 所有方法
emailService = {
sendWelcome: jest.fn().mockResolvedValue(true),
sendReset: jest.fn().mockResolvedValue(true),
};
userService = new UserService(emailService);
});
it('注册后发送欢迎邮件', async () => {
await userService.register({ email: '[email protected]', name: '张三' });
// 验证 mock 被正确调用
expect(emailService.sendWelcome).toHaveBeenCalledOnce();
expect(emailService.sendWelcome).toHaveBeenCalledWith('[email protected]', '张三');
});
// SPY:包装真实实现,观察调用
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
// ...触发可能记录错误的代码...
expect(spy).toHaveBeenCalledWith('expected error message');
spy.mockRestore();
Go:基于接口的 Mock
// Go——使用接口提升可测试性
type EmailSender interface {
SendWelcome(email, name string) error
}
// 测试用 Stub 实现
type stubEmailSender struct {
calls []struct{ email, name string }
shouldFail bool
}
func (s *stubEmailSender) SendWelcome(email, name string) error {
s.calls = append(s.calls, struct{ email, name string }{email, name})
if s.shouldFail {
return errors.New("smtp error")
}
return nil
}
// 测试中使用
func TestRegister_SendsWelcomeEmail(t *testing.T) {
stub := &stubEmailSender{}
svc := NewUserService(stub)
err := svc.Register(context.Background(), User{Email: "[email protected]", Name: "张三"})
require.NoError(t, err)
require.Len(t, stub.calls, 1)
assert.Equal(t, "[email protected]", stub.calls[0].email)
}