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