Mocking in Tests
Mocking in Tests
Section titled “Mocking in Tests”Mocking replaces real dependencies (databases, APIs, email services, clocks) with controlled substitutes so tests run fast, in isolation, and without side effects.
Test Double Types
Section titled “Test Double Types”| Type | Description |
|---|---|
| Stub | Returns a hardcoded value — no verification |
| Mock | Records calls so you can assert they happened |
| Fake | Working implementation (e.g., in-memory database) |
| Spy | Wraps real implementation, records calls |
| Dummy | Placeholder that’s never actually called |
Most frameworks blur these lines — “mock” often means “stub + mock” combined.
When to Mock
Section titled “When to Mock”Mock when:
- The dependency is slow (database, external API)
- The dependency has side effects (email, SMS, payments)
- The dependency is non-deterministic (current time, random numbers)
- You want to test a specific error path that’s hard to trigger
Don’t mock:
- Simple value objects or data structures
- Pure functions with no dependencies
- Your own code that you’re testing
JavaScript / TypeScript (Jest)
Section titled “JavaScript / TypeScript (Jest)”// Auto-mock an entire modulejest.mock('./emailService');
import { sendEmail } from './emailService';
it('sends welcome email on registration', async () => { await userService.register({ email: 'alice@example.com' });
expect(sendEmail).toHaveBeenCalledWith({ to: 'alice@example.com', subject: 'Welcome!', });});
// Control return value(sendEmail as jest.Mock).mockResolvedValueOnce({ success: true });
// Mock only part of a modulejest.mock('./utils', () => ({ ...jest.requireActual('./utils'), // keep other exports real generateId: jest.fn().mockReturnValue('fixed-id'),}));Mocking the Date / Time
Section titled “Mocking the Date / Time”// Jest — freeze timejest.useFakeTimers();jest.setSystemTime(new Date('2025-01-15'));
const result = getExpiryDate(7); // adds 7 daysexpect(result).toEqual(new Date('2025-01-22'));
jest.useRealTimers(); // restore after testMocking HTTP Requests (MSW)
Section titled “Mocking HTTP Requests (MSW)”Mock Service Worker intercepts HTTP requests at the network level:
npm install -D mswimport { http, HttpResponse } from 'msw';import { setupServer } from 'msw/node';
const server = setupServer( http.get('/api/users/:id', ({ params }) => { return HttpResponse.json({ id: params.id, name: 'Alice' }); }));
beforeAll(() => server.listen());afterEach(() => server.resetHandlers());afterAll(() => server.close());
it('displays user name', async () => { render(<UserProfile userId="1" />); expect(await screen.findByText('Alice')).toBeInTheDocument();});Python (pytest-mock / unittest.mock)
Section titled “Python (pytest-mock / unittest.mock)”# Mock a method on an objectdef test_place_order(mocker): mock_save = mocker.patch.object(OrderRepository, 'save', return_value=42)
order_id = order_service.place_order(user_id=1, product_id=10)
assert order_id == 42 mock_save.assert_called_once()
# Mock a function in a moduledef test_send_notification(mocker): mock_send = mocker.patch('myapp.notifications.send_sms') order_service.complete_order(order_id=42) mock_send.assert_called_with('+44...', 'Your order is complete!')
# Mock datetime.now()from datetime import datetime
def test_token_expiry(mocker): fixed_time = datetime(2025, 1, 15, 12, 0, 0) mocker.patch('myapp.auth.datetime').now.return_value = fixed_time
token = create_token(user_id=1, expires_in_hours=24) assert token.expires_at == datetime(2025, 1, 16, 12, 0, 0)C# (Moq)
Section titled “C# (Moq)”var emailService = new Mock<IEmailService>();
emailService .Setup(s => s.SendAsync(It.IsAny<Email>())) .ReturnsAsync(true);
var userService = new UserService(emailService.Object);await userService.RegisterAsync(new RegisterRequest { Email = "alice@example.com" });
// Verify it was calledemailService.Verify( s => s.SendAsync(It.Is<Email>(e => e.To == "alice@example.com")), Times.Once);
// Simulate failureemailService .Setup(s => s.SendAsync(It.IsAny<Email>())) .ThrowsAsync(new SmtpException("Connection refused"));Avoiding Overuse of Mocks
Section titled “Avoiding Overuse of Mocks”Signs of over-mocking:
- Tests break when you refactor but behaviour is unchanged
- Tests are hard to understand because of all the setup
- You’re mocking your own code (not just external dependencies)
If mocking causes pain, consider:
- Using a real in-memory database (SQLite, in-memory Postgres)
- Using a fake implementation (e.g., an in-memory email service)
- Restructuring code so it’s easier to test without mocks