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