Mocking API Calls in Tests
In this page:
Why Mock API Calls in Tests
Making real network requests in tests is slow, depends on an external service actually being available, and can produce different results each run. Mocking replaces the real request with a fake, predictable response, making tests fast, reliable, and independent of any real backend.
Note: Mocked responses should match the real API's shape closely, so your tests stay meaningful if the mock ever drifts from reality.
Warning: Over-mocking can hide real integration bugs — pair unit tests with mocked APIs alongside a smaller number of real integration/E2E tests.
Example: Why Mock API Calls in Tests
// Run in your local React project (npm install required)
test('fetches and displays user', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({ json: () => Promise.resolve({ name: 'Asha' }) })
);
render(<UserProfile userId={1} />);
expect(await screen.findByText('Asha')).toBeInTheDocument();
});
Creating a Mock Function with jest.fn()
jest.fn() creates a special mock function that records how it was called (arguments, call count) and lets you control what it returns, without running any real logic. This is useful for mocking not just fetch, but any function a component depends on.
Note: Use mockFn.mockReturnValue(...) or mockResolvedValue(...) to control what the mock function returns when called.
Warning: A mock function does nothing by default (returns undefined) unless you explicitly configure a return value.
Example: Creating a Mock Function with jest.fn()
// Run in your local React project (npm install required)
const mockOnSave = jest.fn();
test('calls onSave when button clicked', async () => {
const user = userEvent.setup();
render(<SaveButton onSave={mockOnSave} />);
await user.click(screen.getByText('Save'));
expect(mockOnSave).toHaveBeenCalledTimes(1);
});
Resetting Mocks Between Tests
Mock functions and mocked globals (like fetch) can retain state (like call counts) across tests if not reset, causing one test's setup to accidentally affect another. Calling jest.clearAllMocks() or restoring the original implementation in afterEach keeps each test properly isolated.
Note: A common setup pattern is calling jest.clearAllMocks() in a global afterEach, so every test starts with a clean slate automatically.
Warning: Skipping cleanup between tests can cause confusing, hard-to-diagnose failures where a test passes or fails depending on what ran before it.
Example: Resetting Mocks Between Tests
// Run in your local React project (npm install required)
afterEach(() => {
jest.clearAllMocks();
delete global.fetch;
});
test('example test', () => {
global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({}) }));
// ... test body
});
- Making real network requests in tests, which are slow, flaky, and depend on an external service being up.
- Forgetting to reset or restore mocks between tests, letting one test's mock leak into another.
- Mocking fetch's response shape incorrectly, not matching what the real API actually returns.
- Mocking replaces a real API call with a fake, predictable one for testing purposes.
- jest.fn() creates a mock function you can control and inspect within a test.
- Global fetch can be mocked directly, or a library like msw can intercept requests more realistically.
- Resetting mocks between tests (afterEach) avoids one test's setup leaking into another.
Requires npm install (Jest/Vitest) — mocking runs in the Node test environment, not this browser sandbox.
Chapter Quiz — Complete all 9 topics to unlock
0/9 topics done
Complete these topics first: