Mocking API Calls in Tests

Loading

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 the fetch function globally.
  • mockResolvedValueOnce: Mocks a successful response for fetch.
  • mockRejectedValueOnce: Mocks a failed response for fetch.
  • 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.


Leave a Reply

Your email address will not be published. Required fields are marked *