When writing tests for your React components, especially those that make API calls, it’s important to isolate the component’s functionality and avoid hitting real APIs. This is where mocking API calls comes in. By mocking API calls, you can simulate different scenarios (e.g., successful responses, errors) without actually making a network request.
In this guide, we’ll explore how to mock API calls in React tests using libraries like Jest and Mock Service Worker (MSW).
1. Why Mock API Calls?
Mocking API calls during tests helps with:
- Faster tests: Avoiding actual network requests makes tests run faster.
- Consistency: You can simulate consistent API responses and test your components under controlled conditions.
- Error handling: Test how your components handle network failures and error scenarios.
- Isolation: Ensures that tests focus on the component logic and not on external dependencies.
2. Mocking API Calls Using Jest and jest.mock()
One of the simplest ways to mock API calls in React tests is by using Jest’s built-in jest.mock()
method. This allows you to mock modules, including functions used for making API requests like fetch
or axios
.
2.1 Mocking Fetch API
Let’s assume you have a component that fetches data using the fetch
API.
2.1.1 Example: Component Making API Call
// FetchData.js
import React, { useState, useEffect } from 'react';
const FetchData = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
})
.catch(() => {
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
return <div>{data ? JSON.stringify(data) : 'No data found'}</div>;
};
export default FetchData;
2.1.2 Test: Mocking fetch
with Jest
Now, let’s mock the fetch
API using jest.mock()
and simulate a successful API call.
// FetchData.test.js
import { render, screen, waitFor } from '@testing-library/react';
import FetchData from './FetchData';
// Mock the global fetch function
jest.mock('global', () => ({
fetch: jest.fn(),
}));
test('fetches and displays data correctly', async () => {
// Set up the mock fetch function to resolve with a successful response
global.fetch.mockResolvedValueOnce({
json: () => Promise.resolve({ message: 'Success' }),
});
render(<FetchData />);
// Wait for the component to finish loading
await waitFor(() => screen.getByText(/Success/));
// Assert that the data was rendered correctly
expect(screen.getByText(/Success/)).toBeInTheDocument();
});
test('handles error during fetch', async () => {
// Set up the mock fetch function to reject with an error
global.fetch.mockRejectedValueOnce(new Error('Failed to fetch'));
render(<FetchData />);
// Wait for the loading state to be replaced with an error state
await waitFor(() => screen.getByText(/No data found/));
// Assert that the error state was shown
expect(screen.getByText(/No data found/)).toBeInTheDocument();
});
In the above code:
jest.mock('global', ...)
: Mocks thefetch
function globally.mockResolvedValueOnce
: Mocks a successful response forfetch
.mockRejectedValueOnce
: Mocks a failed response forfetch
.waitFor
: Waits for asynchronous updates in the DOM.
3. Mocking Axios API Calls
If your application uses Axios for making API calls, you can mock Axios requests in a similar way.
3.1 Example: Component Using Axios
// FetchData.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const FetchData = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
axios.get('https://api.example.com/data')
.then((response) => {
setData(response.data);
setLoading(false);
})
.catch(() => {
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
return <div>{data ? JSON.stringify(data) : 'No data found'}</div>;
};
export default FetchData;
3.2 Test: Mocking Axios with Jest
To mock Axios in your tests, you can use jest.mock()
to replace Axios with a mock implementation.
// FetchData.test.js
import { render, screen, waitFor } from '@testing-library/react';
import FetchData from './FetchData';
import axios from 'axios';
// Mock the Axios module
jest.mock('axios');
test('fetches and displays data correctly', async () => {
// Mock a successful response from Axios
axios.get.mockResolvedValueOnce({ data: { message: 'Success' } });
render(<FetchData />);
// Wait for the component to finish loading
await waitFor(() => screen.getByText(/Success/));
// Assert that the data was rendered correctly
expect(screen.getByText(/Success/)).toBeInTheDocument();
});
test('handles error during fetch', async () => {
// Mock an error response from Axios
axios.get.mockRejectedValueOnce(new Error('Failed to fetch'));
render(<FetchData />);
// Wait for the loading state to be replaced with an error state
await waitFor(() => screen.getByText(/No data found/));
// Assert that the error state was shown
expect(screen.getByText(/No data found/)).toBeInTheDocument();
});
In the above example:
jest.mock('axios')
: Mocks the Axios module globally.mockResolvedValueOnce
: Mocks a successful response from Axios.mockRejectedValueOnce
: Mocks a failed response from Axios.
4. Mock Service Worker (MSW)
For a more sophisticated and realistic approach, you can use Mock Service Worker (MSW). MSW intercepts actual network requests at the service worker level and allows you to simulate responses.
4.1 Setting Up MSW
First, install MSW:
npm install msw --save-dev
4.2 Example: Mocking API Calls with MSW
Create a mock service worker to intercept requests.
// src/mocks/handlers.js
import { rest } from 'msw';
export const handlers = [
rest.get('https://api.example.com/data', (req, res, ctx) => {
return res(ctx.json({ message: 'Success' }));
}),
];
Set up the MSW server:
// src/mocks/browser.js
import { setupWorker } from 'msw';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
Start the worker in your test setup:
// src/setupTests.js
import { worker } from './mocks/browser';
beforeAll(() => worker.start());
afterAll(() => worker.stop());
4.3 Test: Using MSW
Now, let’s write a test that uses MSW to mock the API call.
// FetchData.test.js
import { render, screen, waitFor } from '@testing-library/react';
import FetchData from './FetchData';
test('fetches and displays data correctly', async () => {
render(<FetchData />);
// Wait for the data to load and be displayed
await waitFor(() => screen.getByText(/Success/));
// Assert that the data was rendered correctly
expect(screen.getByText(/Success/)).toBeInTheDocument();
});
Here, MSW intercepts the network request and returns a mocked response, allowing you to test your components under different scenarios.