When testing React components that rely on external data or API calls, it’s important to mock those API calls so that the tests are predictable, isolated, and run without relying on external services. Mocking API calls allows you to simulate different responses (e.g., successful, error, or loading states) without actually hitting the network.
In this guide, we’ll explore how to mock API calls in your React tests using Jest and React Testing Library.
1. Why Mock API Calls?
Mocking API calls in tests offers several benefits:
- Isolation: Ensures that your tests only focus on the logic and behavior of your components, not external dependencies like APIs.
- Faster Tests: API calls can slow down tests, especially when they’re network-dependent. Mocking allows your tests to run faster.
- Control: You can simulate various scenarios like loading states, successful responses, or errors to see how your component behaves in different situations.
2. Setting Up Jest to Mock API Calls
In a React application, you typically make API calls using fetch
, axios
, or other libraries. For our example, we’ll focus on mocking fetch
API calls using Jest.
3. Example: API Call in a React Component
Let’s assume we have a simple component that fetches user data from an API and displays it.
Example: UserProfile.js
import React, { useState, useEffect } from 'react';
const UserProfile = () => {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://api.example.com/user')
.then(response => response.json())
.then(data => setUser(data))
.catch(error => setError(error));
}, []);
if (error) return <div>Error loading user data</div>;
if (!user) return <div>Loading...</div>;
return <div>User: {user.name}</div>;
};
export default UserProfile;
In this component:
- We use
useEffect
to fetch data when the component mounts. - The API call fetches user data from
https://api.example.com/user
. - The component renders a loading message, then the user’s name, or an error message if the API call fails.
4. Mocking the fetch
API in Jest
To mock the API call, we’ll use Jest’s mocking capabilities to replace the fetch
function with a mock that simulates different responses.
Test: UserProfile.test.js
import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';
// Mock the global fetch function
global.fetch = jest.fn();
describe('UserProfile Component', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('renders loading state initially', () => {
fetch.mockResolvedValueOnce({
json: () => Promise.resolve({ name: 'John Doe' }),
});
render(<UserProfile />);
expect(screen.getByText(/Loading.../i)).toBeInTheDocument();
});
it('renders user data when the API call is successful', async () => {
fetch.mockResolvedValueOnce({
json: () => Promise.resolve({ name: 'John Doe' }),
});
render(<UserProfile />);
// Wait for the user data to load
await waitFor(() => expect(screen.getByText(/User: John Doe/i)).toBeInTheDocument());
});
it('renders error message when the API call fails', async () => {
fetch.mockRejectedValueOnce(new Error('Failed to fetch'));
render(<UserProfile />);
// Wait for the error message to appear
await waitFor(() => expect(screen.getByText(/Error loading user data/i)).toBeInTheDocument());
});
});
5. Explanation of the Test
- Mocking
fetch
: We usejest.fn()
to mock the globalfetch
function, so it doesn’t make actual network requests during the test. Instead, we control whatfetch
returns. mockResolvedValueOnce
: This tells Jest to return a resolved promise with the provided value the next timefetch
is called. In this case, we mock a successful API response by resolving it with user data.mockRejectedValueOnce
: This simulates a failed API call by rejecting the promise.waitFor
: Since the component is asynchronous (waiting for the API call to complete), we usewaitFor
to ensure the test waits until the DOM is updated with the results before making assertions.
6. Different API Response Scenarios
When testing components that make API calls, you might want to simulate different scenarios, such as:
- Successful response
- Failed response
- Loading state
Let’s look at how we can mock these scenarios.
Simulating a Successful API Response
In the previous example, we simulated a successful response with the following code:
fetch.mockResolvedValueOnce({
json: () => Promise.resolve({ name: 'John Doe' }),
});
This simulates a successful response where the json
method of the response resolves to { name: 'John Doe' }
.
Simulating a Failed API Response
You can simulate an error (e.g., network failure or server error) like this:
fetch.mockRejectedValueOnce(new Error('Failed to fetch'));
This simulates an error response, which will cause the catch
block in the UserProfile
component to run, rendering the error message.
Simulating a Delayed API Response (Loading State)
To simulate a delay in the API response (e.g., showing the loading state), you can mock fetch
with a delay:
fetch.mockResolvedValueOnce({
json: () => new Promise(resolve => setTimeout(() => resolve({ name: 'John Doe' }), 2000)),
});
This mocks a delayed response, which allows you to test how the component behaves when it’s waiting for data.
7. Mocking API Calls with Axios
If you are using Axios instead of fetch
, the approach is similar, but you would mock axios
instead. Here’s how you can do it.
Example: Using Axios for API Calls
import axios from 'axios';
import React, { useState, useEffect } from 'react';
const UserProfile = () => {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
axios.get('https://api.example.com/user')
.then(response => setUser(response.data))
.catch(error => setError(error));
}, []);
if (error) return <div>Error loading user data</div>;
if (!user) return <div>Loading...</div>;
return <div>User: {user.name}</div>;
};
export default UserProfile;
Mocking Axios in Jest
import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';
import axios from 'axios';
// Mock axios
jest.mock('axios');
describe('UserProfile Component with Axios', () => {
it('renders loading state initially', () => {
axios.get.mockResolvedValueOnce({ data: { name: 'John Doe' } });
render(<UserProfile />);
expect(screen.getByText(/Loading.../i)).toBeInTheDocument();
});
it('renders user data when the API call is successful', async () => {
axios.get.mockResolvedValueOnce({ data: { name: 'John Doe' } });
render(<UserProfile />);
await waitFor(() => expect(screen.getByText(/User: John Doe/i)).toBeInTheDocument());
});
it('renders error message when the API call fails', async () => {
axios.get.mockRejectedValueOnce(new Error('Failed to fetch'));
render(<UserProfile />);
await waitFor(() => expect(screen.getByText(/Error loading user data/i)).toBeInTheDocument());
});
});
8. Best Practices for Mocking API Calls
- Use
jest.clearAllMocks()
: After each test, clear mocks to ensure tests don’t interfere with each other. - Use Mock Responses to Simulate Real Scenarios: Test various edge cases such as successful responses, failures, and timeouts.
- Mock External APIs: Always mock external services or APIs in tests to avoid real network requests and ensure test consistency.