How to Test Fetch API in React
Testing API calls is crucial for ensuring the reliability of React
applications. This article provides a straightforward guide on how to
test the Fetch API in React components using Jest and React Testing
Library. We will cover how to mock the global fetch
function, write clean test cases, and assert that your components handle
API responses and asynchronous rendering correctly.
The Strategy: Mocking the Global Fetch
When testing React components, you should avoid making actual network
requests to real backend APIs. Real requests slow down tests and
introduce external dependencies that can cause tests to fail. Instead,
you should mock the global fetch function provided by the
browser environment.
By mocking fetch, you can control exactly what the API
returns (success data, empty states, or errors) and verify how your
component behaves under different scenarios.
Step-by-Step Implementation
1. Create the React Component
First, consider a simple component that fetches a user’s name from an API when it mounts and displays it.
import React, { useState, useEffect } from 'react';
export default function UserProfile() {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://api.example.com/user')
.then((res) => {
if (!res.ok) {
throw new Error('Network response was not ok');
}
return res.json();
})
.then((data) => setUser(data))
.catch((err) => setError(err.message));
}, []);
if (error) return <div>Error: {error}</div>;
if (!user) return <div>Loading...</div>;
return <div>User: {user.name}</div>;
}2. Mock Fetch in Your Test File
Using Jest, you can spy on the global.fetch object and
mock its resolved value. Because fetch returns a Promise
that resolves to a Response object (which itself has a
.json() method that returns a Promise), your mock must
replicate this structure.
Here is the test suite using React Testing Library:
import { render, screen } from '@testing-library/react';
import UserProfile from './UserProfile';
describe('UserProfile Component', () => {
beforeEach(() => {
// Clear mocks before each test
jest.restoreAllMocks();
});
test('fetches and displays user data successfully', async () => {
const mockUser = { name: 'John Doe' };
// Mock global fetch to return a successful response
jest.spyOn(global, 'fetch').mockResolvedValue({
ok: true,
json: jest.fn().mockResolvedValue(mockUser),
});
render(<UserProfile />);
// Assert that the loading state is shown initially
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Assert that the fetched data is eventually displayed
const nameElement = await screen.findByText('User: John Doe');
expect(nameElement).toBeInTheDocument();
// Assert fetch was called with the correct URL
expect(global.fetch).toHaveBeenCalledWith('https://api.example.com/user');
});
test('displays error message when the API fetch fails', async () => {
// Mock global fetch to reject the request
jest.spyOn(global, 'fetch').mockResolvedValue({
ok: false,
});
render(<UserProfile />);
// Assert that the error state is displayed
const errorElement = await screen.findByText('Error: Network response was not ok');
expect(errorElement).toBeInTheDocument();
});
});Key Testing Practices
- Use
findByqueries for asynchronous elements: Always use React Testing Library’sfindBy*queries (likefindByText) when waiting for elements that render after an API call completes. These queries automatically retry until the element appears or the timeout is reached. - Restore mocks: Use
jest.restoreAllMocks()in thebeforeEachorafterEachhook to ensure that mock implementations do not bleed over into other tests. - Test both success and failure paths: Always write separate test cases to verify how your UI reacts to successful data retrieval, loading states, and network errors.