Mocking
In short
Mocking is a testing technique that replaces a real dependency, such as a database or API, with a fake you control so that code can be tested in isolation.
What is mocking?
Mocking means swapping a real dependency of the code you are testing for a stand-in object, called a mock, that you control completely. The dependency might be a database, a payment API, the system clock, or an email service. With a mock in place, a test runs quickly and predictably without touching the real thing.
A mock is programmed with canned responses, such as returning a specific user when asked for ID 42, and it records how it was called. The test can then check both the result of the code and its interactions, for example that the email service was called exactly once with the right address. Most test frameworks include helpers for creating mocks, and dependency injection makes them easy to pass in.
An analogy is a flight simulator: pilots practice emergencies without risking a real plane, because the simulator behaves exactly as instructed. Mocks are used heavily in unit tests, for simulating errors that are hard to trigger for real, like a network timeout, and for avoiding side effects such as charging a real credit card.
A mock is one kind of test double, the general term for any fake used in tests. A stub only returns fixed data, a fake is a simpler working implementation such as an in-memory database, and a mock also verifies how it was used. Mocking too much is a common mistake, because tests can pass while the real integration is broken, which is why integration tests are still needed.
Key takeaways
- A mock replaces a real dependency with a controllable fake.
- Mocks make tests fast, repeatable, and free of side effects.
- Mocks can verify how they were called, not just return data.
- Stubs, fakes, and mocks are all types of test doubles.
- Over-mocking can hide real integration bugs.
Example
import { test, mock } from "node:test";
import assert from "node:assert/strict";
async function welcomeUser(user, emailService) {
await emailService.send(user.email, "Welcome!");
}
test("sends a welcome email", async () => {
const emailService = { send: mock.fn(async () => {}) }; // no real email is sent
await welcomeUser({ email: "ada@example.com" }, emailService);
assert.equal(emailService.send.mock.callCount(), 1);
assert.deepEqual(emailService.send.mock.calls[0].arguments, ["ada@example.com", "Welcome!"]);
});Readers ask
What is the difference between a mock and a stub?
A stub simply returns predefined data so the code under test can run. A mock also records how it was called, so the test can verify interactions, such as whether a method was called and with which arguments.
When should you not use mocks?
Avoid mocking simple, fast code you own, and avoid mocking so much that the test only checks your mocks. To verify that real components work together, such as queries against a database, use integration tests instead.
See also
- Unit TestTesting & Quality, p. 35A unit test is a small, automated check that verifies one function, method, or class behaves correctly in isolation from the rest of the program.
- Integration TestTesting & Quality, p. 12An integration test is an automated test that checks whether several parts of a system, such as code, a database, and an API, work correctly together.
- Dependency InjectionSoftware Architecture, p. 12Dependency injection is a design technique in which an object receives the other objects it needs from the outside instead of creating them itself.
- Test-Driven DevelopmentTesting & Quality, p. 34Test-driven development is a coding practice in which you write a failing test first, then write just enough code to pass it, and then clean up the design.
- APIBackend & APIs, p. 2An API is a set of rules that lets one piece of software request data or actions from another in a predictable, documented way.
Spotted a mistake or something missing on this page?Suggest an edit