Mock & Stub Guide

Test Double Types

TypeDefinitionVerifies?Returns value?Use When
DummyPassed but never usedNoNoFilling required parameters
StubReturns canned answersNoYes (fixed)Controlling indirect inputs
FakeWorking implementation, simplifiedNoYes (real logic)In-memory DB, fake email sender
SpyRecords calls; can assert on themYes (post-hoc)OptionalVerify side effects without strict pre-setup
MockPre-programmed with expectationsYes (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