How to Test REST APIs in React

Testing REST APIs in React is essential for ensuring your application fetches, displays, and handles data correctly. This article provides a straightforward guide on how to test API calls in React components using modern testing tools like Jest, React Testing Library, and Mock Service Worker (MSW). You will learn the best practices for mocking network requests to write reliable, isolated integration tests without hitting actual backend servers.

Why Mock REST APIs in React Tests?

When testing React components that interact with REST APIs, you should never make actual network requests to real servers. Real API calls make tests slow, flaky, dependent on internet connectivity, and prone to failure if the backend data changes.

Instead, you should mock the API responses. Mocking allows you to: * Ensure tests run fast and offline. * Deliver consistent, predictable data to your components. * Easily test edge cases, such as slow loading states, network timeouts, and server errors (e.g., 500 Internal Server Error).

The Best Tool for the Job: Mock Service Worker (MSW)

While you can mock fetch or axios directly using Jest mocks, the industry-standard approach is to use Mock Service Worker (MSW). MSW intercepts requests at the network level using a Service Worker (in the browser) or a library-level interceptor (in Node.js/Jest). This means your React code remains completely unaware that it is being mocked, making your tests highly realistic.

Step 1: Install the Required Tools

First, install React Testing Library and MSW in your project:

npm install --save-dev msw @testing-library/react @testing-library/jest-dom

Step 2: Create Mock API Handlers

Define the mock REST API endpoints and the mock data they should return. Create a file named src/mocks/handlers.js:

import { http, HttpResponse } from 'msw';

export const handlers = [
  // Intercept GET requests to /api/users
  http.get('https://api.example.com/users', () => {
    return HttpResponse.json([
      { id: 1, name: 'John Doe' },
      { id: 2, name: 'Jane Smith' },
    ]);
  }),
];

Step 3: Set Up the Mock Server

Create a mock server instance for your test environment. Create a file named src/mocks/server.js:

import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);

Step 4: Configure Jest to Use the Mock Server

Configure your testing environment to start the mock server before running tests, reset the mock handlers after each test, and close the server when tests are finished. Add this to your src/setupTests.js file:

import '@testing-library/jest-dom';
import { server } from './mocks/server';

// Establish API mocking before all tests.
beforeAll(() => server.listen());

// Reset any request handlers that we may declare during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers());

// Clean up after the tests are finished.
afterAll(() => server.close());

Writing the Test

Consider a simple React component that fetches users from an API and renders them:

// src/UserList.js
import React, { useState, useEffect } from 'react';

export default function UserList() {
  const [users, setUsers] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/users')
      .then((res) => {
        if (!res.ok) throw new Error('Failed to fetch');
        return res.json();
      })
      .then((data) => setUsers(data))
      .catch((err) => setError(err.message));
  }, []);

  if (error) return <p>Error: {error}</p>;
  if (users.length === 0) return <p>Loading...</p>;

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Here is how you test this component using React Testing Library and Jest to verify the successful API call:

// src/UserList.test.js
import { render, screen } from '@testing-library/react';
import UserList from './UserList';

test('fetches and displays users successfully', async () => {
  render(<UserList />);

  // Verify loading state is shown initially
  expect(screen.getByText(/loading/i)).toBeInTheDocument();

  // Wait for the mock API data to render in the DOM
  const firstUser = await screen.findByText('John Doe');
  const secondUser = await screen.findByText('Jane Smith');

  expect(firstUser).toBeInTheDocument();
  expect(secondUser).toBeInTheDocument();
  expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
});

Testing Error States

To test how your application behaves when the REST API fails, you can override the default handlers for a specific test:

import { render, screen } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
import { server } from './mocks/server';
import UserList from './UserList';

test('handles server error states gracefully', async () => {
  // Override the handler for this test to return a 500 error
  server.use(
    http.get('https://api.example.com/users', () => {
      return new HttpResponse(null, { status: 500 });
    })
  );

  render(<UserList />);

  // Wait for the error message to render
  const errorMessage = await screen.findByText(/error: failed to fetch/i);
  expect(errorMessage).toBeInTheDocument();
});