In a real flight control center, simulators are used to train pilots without risking damage to a real ship. In React tests, mocking serves exactly the same role -- it lets you test components without real connections to APIs, databases, or external services.
jest.mock() lets you replace an entire module with its mock:1// Mock the entire API module
2jest.mock('./api/missionApi');
3
4import { fetchMission, launchMission } from './api/missionApi';
5
6test('displays mission data', async () => {
7 // Set what the mock should return
8 fetchMission.mockResolvedValue({
9 id: 1,
10 name: 'Apollo 13',
11 status: 'Active'
12 });
13
14 render(<MissionPanel />);
15
16 expect(await screen.findByText('Apollo 13')).toBeInTheDocument();
17 expect(fetchMission).toHaveBeenCalledTimes(1);
18});1jest.mock('./utils/navigation', () => ({
2 calculateRoute: jest.fn((from, to) => ({
3 distance: 100,
4 duration: '2 hours',
5 fuel: 50
6 })),
7 isValidCoordinate: jest.fn(() => true),
8}));1jest.mock('./utils/spacemath', () => ({
2 ...jest.requireActual('./utils/spacemath'),
3 // Override ONLY one function
4 calculateGravity: jest.fn(() => 9.81),
5}));1beforeEach(() => {
2 global.fetch = jest.fn();
3});
4
5afterEach(() => {
6 jest.restoreAllMocks();
7});
8
9test('fetches planets from API', async () => {
10 fetch.mockResolvedValueOnce({
11 ok: true,
12 json: () => Promise.resolve([
13 { id: 1, name: 'Mars' },
14 { id: 2, name: 'Jupiter' },
15 ]),
16 });
17
18 render(<PlanetList />);
19
20 expect(await screen.findByText('Mars')).toBeInTheDocument();
21 expect(screen.getByText('Jupiter')).toBeInTheDocument();
22 expect(fetch).toHaveBeenCalledWith('/api/planets');
23});1jest.mock('axios');
2import axios from 'axios';
3
4test('posts new mission', async () => {
5 axios.post.mockResolvedValue({
6 data: { id: 42, name: 'New Mission', status: 'Created' }
7 });
8
9 render(<CreateMissionForm />);
10
11 await user.type(screen.getByLabelText('Mission Name'), 'Artemis');
12 await user.click(screen.getByRole('button', { name: 'Create' }));
13
14 expect(axios.post).toHaveBeenCalledWith('/api/missions', {
15 name: 'Artemis'
16 });
17 expect(await screen.findByText('Mission created!')).toBeInTheDocument();
18});Mocking
fetch or axios directly works, but has a drawback -- you're testing implementation (whether you used fetch or axios), not behavior (whether data was fetched). MSW solves this problem by intercepting requests at the network level. It's like a simulator that mimics real satellite communication -- your component "thinks" it's talking to a real server, but in reality MSW generates the responses. This makes the test more realistic and resistant to implementation changes:1import { rest } from 'msw';
2import { setupServer } from 'msw/node';
3
4// Define handlers
5const handlers = [
6 rest.get('/api/missions', (req, res, ctx) => {
7 return res(
8 ctx.json([
9 { id: 1, name: 'Apollo 11', status: 'Completed' },
10 { id: 2, name: 'Artemis I', status: 'Active' },
11 ])
12 );
13 }),
14
15 rest.post('/api/missions', (req, res, ctx) => {
16 return res(
17 ctx.status(201),
18 ctx.json({ id: 3, name: req.body.name, status: 'Created' })
19 );
20 }),
21];
22
23// Create server
24const server = setupServer(...handlers);
25
26// Setup and teardown
27beforeAll(() => server.listen());
28afterEach(() => server.resetHandlers());
29afterAll(() => server.close());
30
31test('lists missions from API', async () => {
32 render(<MissionList />);
33
34 expect(await screen.findByText('Apollo 11')).toBeInTheDocument();
35 expect(screen.getByText('Artemis I')).toBeInTheDocument();
36});1test('handles server error', async () => {
2 // Only for this test -- return an error
3 server.use(
4 rest.get('/api/missions', (req, res, ctx) => {
5 return res(ctx.status(500), ctx.json({ error: 'Server error' }));
6 })
7 );
8
9 render(<MissionList />);
10
11 expect(await screen.findByText('Failed to load missions')).toBeInTheDocument();
12});Many components use
setTimeout or setInterval -- for example, auto-refreshing data every 30 seconds, mission launch countdown, or animations. In tests, we don't want to wait a real 30 seconds! Jest provides fake timers that let you "fast-forward time" like in a flight simulator. Call jest.useFakeTimers(), then jest.advanceTimersByTime(ms) to skip by a specified number of milliseconds:1test('auto-refreshes data every 30 seconds', () => {
2 jest.useFakeTimers();
3 const mockFetch = jest.fn().mockResolvedValue({ data: [] });
4
5 render(<AutoRefreshPanel fetchData={mockFetch} interval={30000} />);
6
7 // First call on mount
8 expect(mockFetch).toHaveBeenCalledTimes(1);
9
10 // After 30 seconds -- second call
11 act(() => jest.advanceTimersByTime(30000));
12 expect(mockFetch).toHaveBeenCalledTimes(2);
13
14 // After another 30 seconds
15 act(() => jest.advanceTimersByTime(30000));
16 expect(mockFetch).toHaveBeenCalledTimes(3);
17
18 jest.useRealTimers();
19});