What is mocking in testing and when should you use it?
- Technology:
- Software Testing
Quick answer
Mocking replaces a real dependency with a controllable fake so a test can run in isolation and check how the code interacts with it; use it for slow, non-deterministic or external dependencies, not for everything.
Detailed explanation
Test doubles come in a few flavours. A stub returns canned answers, a mock also records calls so you can assert on them, a spy wraps a real function to observe it, and a fake is a working lightweight implementation such as an in-memory repository.
Mock at the boundaries of your system: payment providers, email services, the clock, random numbers, network calls. That keeps tests fast and deterministic and lets you simulate failures that are hard to trigger for real, such as a timeout.
Over-mocking is the common mistake. When a test mocks every collaborator and asserts on internal calls, it breaks on every refactor while still passing when the real integration is broken. Prefer asserting on outputs and observable behaviour, and cover real integrations with a smaller set of integration tests.
Example
test('sends a receipt after payment', async () => {
const email = { send: jest.fn().mockResolvedValue(undefined) };
const payments = { charge: jest.fn().mockResolvedValue({ id: 'pay_1' }) };
await checkout({ email, payments }, { userId: 7, total: 50 });
expect(payments.charge).toHaveBeenCalledWith(7, 50);
expect(email.send).toHaveBeenCalledWith(7, expect.stringContaining('pay_1'));
});Key points
- Stub, mock, spy and fake are different kinds of test doubles
- Mock external, slow or non-deterministic dependencies
- Over-mocking couples tests to implementation details
Common mistakes
- Mocking the module under test, so the test proves nothing.
- Asserting on every internal call instead of on results.
- Mocking the database in all tests and never testing real queries.
Follow-up questions
- What is dependency injection and how does it help testing?