We use cookies to enhance your experience on the site
CodeWorlds

Mocking Modules and API -- Flight Simulator

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() -- Replacing Modules

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});

Mocking with Implementation

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}));

Partial Mocking

1jest.mock('./utils/spacemath', () => ({
2  ...jest.requireActual('./utils/spacemath'),
3  // Override ONLY one function
4  calculateGravity: jest.fn(() => 9.81),
5}));

Mocking fetch / axios

Mocking Global fetch

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});

Mocking axios

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});

MSW (Mock Service Worker) -- Advanced API Mocking

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});

Overriding Handlers in a Single Test

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});

Mocking Timers

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});
Go to CodeWorlds