↓Skip to main content
  1. Testing & Quality/

Mocks, Stubs, and Fakes: Choosing Test Doubles

·350 words·2 mins·
Testing Mocks Software Design Unit Testing
Ifarra
Author
Ifarra
Disturbing the peace!!
Table of Contents

A test double is a substitute for a dependency. It can make a test deterministic, but every substitute also makes the test less representative of the real system. The useful question is not “how can every dependency be mocked?” but “which boundary needs control for this test?”

A comparison of stubs, mocks, fakes, and spies, with examples of when each is useful
Use test doubles at boundaries, not as a replacement for every collaborator.

Four common kinds
#

  • Stub: returns a prepared value, such as a fixed API response.
  • Mock: records or constrains interactions, such as whether an email notification was requested.
  • Fake: a lightweight working implementation, such as an in-memory repository.
  • Spy: wraps a real function while recording how it was called.

Libraries use these terms differently, but the purposes help decide what a test actually needs.

Substitute an external boundary
#

For example, a notification service might accept a sender dependency:

export async function inviteMember(email, sendEmail) {
  if (!email.includes("@")) throw new Error("Invalid email");
  await sendEmail({ to: email, template: "invite" });
  return { invited: true };
}

A unit test can pass a simple function that records the request. This checks that valid input triggers the expected notification without sending real email. The invalid-email test should also prove that no message is sent.

Prefer fakes when state matters
#

An in-memory fake repository can make it possible to test behavior such as “an invitation is only created once” without mocking every method call. Keep its behavior close to the production contract, and add integration coverage for the real database adapter.

Avoid over-mocking
#

Tests that assert every internal call can fail after harmless refactoring. Worse, they can pass even if real collaborators no longer agree on data shapes. Mock only where you need a controllable boundary or a side effect, and test important component wiring with real or faithful collaborators.

A quick choice
#

  • Need a fixed answer? Use a stub.
  • Need a small stateful substitute? Use a fake.
  • Need to verify a significant side effect? Use a mock.
  • Need to observe a real function? Use a spy.

Choose the simplest double that gives the test control without undermining the behavior it should prove.

Related

The Testing Pyramid: Unit, Integration, and End-to-End Tests
·1982 words·10 mins
Testing Software Quality Unit Testing Integration Testing
A practical guide to the testing pyramid, with examples of what belongs in unit, integration, and end-to-end tests.
Unit Testing JavaScript with Vitest
·407 words·2 mins
JavaScript Vitest Testing Unit Testing
Set up Vitest and use Arrange–Act–Assert to test pure functions without depending on a browser or network.
pytest Basics: Tests, Fixtures, and Clear Assertions
·1984 words·10 mins
Python pytest Testing Unit Testing
Write discoverable pytest functions, organize reusable state with fixtures, and make failures easy to understand.