Real code depends on other things: APIs, databases, timers, and other modules. Mocking replaces those dependencies with controlled fakes so you can test your code in isolation. This lesson introduces the concept before we dig into specific mocking tools.
What Mocking Means
A mock is a fake version of a function, object, or module that stands in for the real thing during a test. Instead of calling a real API or database, your code calls the mock, which returns a value you control directly — making the test fast, deterministic, and independent of network conditions or external state.
Jest provides mocking tools at every level: jest.fn() for individual functions, jest.spyOn() for existing object methods, and jest.mock() for entire modules. All three feed into the same expect().toHaveBeenCalled...() family of matchers for verifying how the mock was used.
No real email is sent; the mock records the call so you can assert on it directly.
Three Levels of Mocking
jest.fn(); // mock a standalone function
jest.spyOn(obj, 'method'); // wrap an existing method, optionally keeping real behavior
jest.mock('./api'); // replace an entire module with a mock
jest.fn() creates a brand-new mock function from scratch.
jest.spyOn() wraps an existing method on a real object, so you can observe or override it.
jest.mock() replaces an entire imported module with an auto-mocked or manually-defined version.
All mock functions track calls, arguments, and return values automatically.
Mocking Cheat Sheet
The core mocking tools Jest provides and when to reach for each.
Tool
Use For
jest.fn()
A brand-new fake function, e.g. a callback
jest.spyOn(obj, 'method')
Watching or overriding a real object's method
jest.mock('./module')
Replacing an entire imported module
mockFn.mockReturnValue(x)
Making a mock return a fixed value
mockFn.mockResolvedValue(x)
Making a mock return a resolved promise
expect(mockFn).toHaveBeenCalled()
Asserting the mock was invoked
jest.clearAllMocks()
Resetting call history between tests
Why Mock Dependencies at All
Testing against a real database, external API, or file system makes tests slow, flaky (network failures, rate limits), and hard to set up in CI. Mocking removes that dependency for the specific test, letting you focus purely on whether *your* code behaves correctly given a certain input from that dependency.
Mocking is not about avoiding testing entirely — you still need some integration or end-to-end tests that use real dependencies. Mocks are for unit tests that isolate a single piece of logic.
Faster tests: no real network calls or database queries.
Deterministic tests: the mock always returns exactly what you tell it to.
Easy to simulate edge cases: errors, timeouts, and empty responses.
Isolates the code under test from unrelated failures elsewhere in the system.
The Trade-Offs of Over-Mocking
Excessive mocking can make tests pass while real integration is broken, because the mock never verifies that the fake behavior matches reality. A healthy test suite mixes unit tests (heavily mocked, fast, focused) with a smaller number of integration tests (fewer mocks, closer to production behavior).
Approach
Trade-off
Mock everything
Fast, but may miss real integration bugs
Mock nothing
Realistic, but slow and often flaky
Balanced
Unit tests mock dependencies; a few integration tests don't
Common Mistakes
Mocking so much of the system that the test no longer verifies anything meaningful.
Forgetting to reset mocks between tests, leaking call history and causing confusing failures.
Mocking a dependency that should actually be tested directly (e.g. a pure utility function).
Never writing any integration tests, relying entirely on mocked unit tests for confidence.
Key Takeaways
Mocking replaces a real dependency with a controlled fake during a test.
Jest offers jest.fn(), jest.spyOn(), and jest.mock() for different mocking needs.
Mocking makes tests faster and more deterministic by removing external dependencies.
A healthy suite balances mocked unit tests with some non-mocked integration tests.
Pro Tip
Before reaching for a mock, ask: "is this dependency slow, unpredictable, or external?" If yes, mock it. If it's a fast, pure function within your own codebase, consider testing it directly instead.
You now understand what mocking is and why it matters. Next, take a closer look at mock functions and everything they can do.