How to use jest.fn(), jest.mock(), and jest.spyOn() (or their Vitest equivalents) to isolate code under test, plus API mocking with MSW for realistic network request testing.
jest.fn() or vi.fn() creates standalone mock functions that record calls, arguments, and return values, configurable with mockReturnValue and mockImplementation.
jest.mock() or vi.mock() replaces entire modules with mocks. Use jest.requireActual for partial mocking to keep some exports real while mocking others.
jest.spyOn() or vi.spyOn() watches existing methods without replacing them by default, recording calls while preserving original behavior with easy restoration.
Mock Service Worker intercepts HTTP requests at the network level, working with any HTTP client and providing the most realistic API mocking for integration tests.
Mock external dependencies and services you do not control. Avoid over-mocking internal modules, which creates brittle tests coupled to implementation details.
Mocking is the practice of replacing real dependencies with controlled substitutes during testing. It isolates the code under test so you can verify its behavior without relying on databases, APIs, or other modules. Jest and Vitest share nearly identical mocking APIs, making these patterns transferable between both runners.
Creates a standalone mock function that records all calls, arguments, and return values:
const mockCallback = jest.fn();
mockCallback('hello');
expect(mockCallback).toHaveBeenCalledTimes(1);
expect(mockCallback).toHaveBeenCalledWith('hello');Mock functions can be configured to return specific values:
mockReturnValue(value): Always returns the specified valuemockReturnValueOnce(value): Returns the value once, then falls back to defaultmockImplementation(fn): Replaces the function body entirelymockResolvedValue(value): Returns a resolved promise (shorthand for async mocks)mockRejectedValue(error): Returns a rejected promiseReplaces an entire module with a mock version. Jest hoists jest.mock() calls to the top of the file automatically:
jest.mock('./api'); // all exports become jest.fn()
import { fetchUsers } from './api';
fetchUsers.mockResolvedValue([{ name: 'Alice' }]);For partial mocking (mock some exports, keep others real):
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
formatDate: jest.fn(() => '2024-01-01'),
}));Watches an existing method without replacing it (by default), recording calls while preserving original behavior:
const spy = jest.spyOn(console, 'log');
someFunction();
expect(spy).toHaveBeenCalledWith('expected output');
spy.mockRestore(); // restore original implementationUse .mockImplementation() on a spy to override the behavior while still being able to restore it later.
Key matchers for verifying mock behavior:
toHaveBeenCalled(): Function was called at least oncetoHaveBeenCalledTimes(n): Called exactly n timestoHaveBeenCalledWith(args): Called with specific argumentstoHaveBeenLastCalledWith(args): Last call had these argumentstoHaveBeenNthCalledWith(n, args): Nth call had these argumentsMSW intercepts HTTP requests at the network level, providing the most realistic approach to API mocking:
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
http.get('/api/users', () => {
return HttpResponse.json([{ id: 1, name: 'Alice' }]);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());MSW advantages over mocking fetch directly: works with any HTTP client (axios, fetch, ky), tests the full request/response cycle, and can be reused between tests and development.
Always clear mocks between tests to prevent state leakage:
jest.clearAllMocks() / vi.clearAllMocks(): Resets call history and return valuesjest.restoreAllMocks() / vi.restoreAllMocks(): Also restores original implementationsrestoreMocks: true in your config for automatic cleanupFun Fact
MSW (Mock Service Worker) was created by Artem Zakharchenko, a developer who was frustrated with mocking fetch in tests. MSW uses actual Service Worker API in the browser and request interception in Node.js, making it the only mocking tool that works identically in both environments.