Mocking & Stubbing

When writing Unit Tests, the code under test must be completely isolated from external dependencies (databases, APIs, time, random number generators). To achieve this isolation, we use "Test Doubles"—objects that look and behave like their production counterparts but are strictly controlled by the test.

1. Stubs

A Stub is the simplest form of a test double. It simply provides canned answers to calls made during the test. It does not record how it was called; it just returns pre-programmed data.

// Stub Example
const stubbedUserRepository = {
    getUserById: (id) => {
        // Always returns this static data, regardless of input
        return { id: 1, name: "Alice", active: true };
    }
};

const userService = new UserService(stubbedUserRepository);
const user = userService.getActiveUser(1);
expect(user.name).toBe("Alice");

2. Mocks

Mocks are more complex. They are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can verify how they were used.

// Mock Example (using Jest syntax)
const mockEmailService = {
    send: jest.fn() // Creates a mock function that tracks calls
};

const authService = new AuthService(mockEmailService);
authService.registerUser("bob@example.com", "password");

// We assert on the BEHAVIOR of the code, not just the state
expect(mockEmailService.send).toHaveBeenCalledTimes(1);
expect(mockEmailService.send).toHaveBeenCalledWith("bob@example.com", "Welcome!");

3. Spies

A Spy is a wrapper around an actual function or object. It allows the real function to execute normally, but it secretly records arguments, return values, and call counts so you can assert on them later.

4. The Danger of Over-Mocking

While mocks are essential for unit testing, abusing them leads to fragile tests.

[!IMPORTANT] Rule of Thumb: Use Stubs to control the inputs to your system under test. Use Mocks to verify the outputs/side-effects of your system under test.