Mock & Stub Guide
Test Double Types
| Type | Definition | Verifies? | Returns value? | Use When |
|---|---|---|---|---|
| Dummy | Passed but never used | No | No | Filling required parameters |
| Stub | Returns canned answers | No | Yes (fixed) | Controlling indirect inputs |
| Fake | Working implementation, simplified | No | Yes (real logic) | In-memory DB, fake email sender |
| Spy | Records calls; can assert on them | Yes (post-hoc) | Optional | Verify side effects without strict pre-setup |
| Mock | Pre-programmed with expectations | Yes (strict) | Yes (configured) | Verify exact interactions upfront |
Jest: Mock vs Spy
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
import { EmailService } from './EmailService';
import { UserService } from './UserService';
describe('UserService', () => {
let emailService: jest.Mocked<EmailService>;
let userService: UserService;
beforeEach(() => {
// MOCK: replaces implementation, auto-mocks all methods
emailService = {
sendWelcome: jest.fn().mockResolvedValue(true),
sendReset: jest.fn().mockResolvedValue(true),
} as jest.Mocked<EmailService>;
userService = new UserService(emailService);
});
it('sends welcome email after registration', async () => {
await userService.register({ email: 'user@test.com', name: 'Alice' });
// Verify the mock was called correctly
expect(emailService.sendWelcome).toHaveBeenCalledOnce();
expect(emailService.sendWelcome).toHaveBeenCalledWith('user@test.com', 'Alice');
});
it('stub: return different values per call', async () => {
emailService.sendWelcome
.mockResolvedValueOnce(false) // first call fails
.mockResolvedValueOnce(true); // second call succeeds
// Test retry logic...
});
});
// SPY: wrap real implementation, observe calls
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
// ... code that might log errors ...
expect(spy).toHaveBeenCalledWith('expected error message');
spy.mockRestore();
Go: Interface-Based Mocks
// Go โ use interfaces for testability
// 1. Define interface
type EmailSender interface {
SendWelcome(email, name string) error
SendReset(email, token string) error
}
// 2. Stub implementation for tests
type stubEmailSender struct {
sendWelcomeCalls []struct{ email, name string }
shouldFail bool
}
func (s *stubEmailSender) SendWelcome(email, name string) error {
s.sendWelcomeCalls = append(s.sendWelcomeCalls, struct{ email, name string }{email, name})
if s.shouldFail {
return errors.New("smtp error")
}
return nil
}
// 3. Use in test
func TestRegister_SendsWelcomeEmail(t *testing.T) {
stub := &stubEmailSender{}
svc := NewUserService(stub)
err := svc.Register(context.Background(), User{Email: "u@test.com", Name: "Alice"})
require.NoError(t, err)
require.Len(t, stub.sendWelcomeCalls, 1)
assert.Equal(t, "u@test.com", stub.sendWelcomeCalls[0].email)
}
// Use mockery or moq for auto-generated mocks:
// //go:generate mockery --name=EmailSender
// mockery generates MockEmailSender struct with full mock capabilities