Introduction
Test doubles replace real dependencies during testing. Understanding the difference between mocks, stubs, and spies helps you choose the right tool.
Key Concepts
Types of test doubles:
- Stub: Returns pre-programmed responses
- Spy: Records calls while using real implementation
- Mock: Stubs with verification of how it was called
Deep Dive
Creating Stubs
typescript// Stub - just returns a value const getUserStub = jest.fn().mockResolvedValue({ id: '1', name: 'John', });
Creating Spies
typescript// Spy - calls real implementation and records const consoleSpy = jest.spyOn(console, 'log'); await doSomething(); expect(consoleSpy).toHaveBeenCalledWith('Expected message'); consoleSpy.mockRestore();
Full Mock with Verification
typescriptconst mockPrisma = { user: { findUnique: jest.fn(), create: jest.fn(), }, }; jest.mock('@/lib/prisma', () => ({ prisma: mockPrisma })); it('creates user with correct data', async () => { await createUser({ name: 'John' }); expect(mockPrisma.user.create).toHaveBeenCalledWith({ data: { name: 'John' }, }); });
Mock Return Values
typescriptmock.mockReturnValue(42); // Sync mock.mockResolvedValue({ data }); // Async success mock.mockRejectedValue(new Error()); mock.mockImplementation((x) => x * 2);
Summary
Use stubs when you only need controlled return values, spies when you want to observe real behavior, and full mocks when you need both controlled behavior and call verification.