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) }