Mocking API Requests with Jest

Introduction

When building React applications, your components often depend on data fetched from APIs — whether it’s a list of users, products, or posts. Testing these components directly with real API requests can be slow, unreliable, and dependent on external servers.

That’s why mocking API requests is such an essential part of writing unit and integration tests.

Mocking allows you to simulate the behavior of APIs without actually making network calls. This makes your tests:

  • Faster — No waiting for real HTTP responses.
  • More Reliable — No dependency on internet connectivity or external APIs.
  • Deterministic — You have full control over the responses and errors.

In this post, we’ll explore how to mock API requests using Jest, one of the most popular testing frameworks for JavaScript and React. We’ll learn how to mock Axios calls, simulate different types of responses, and handle both success and failure cases efficiently.


Why Mock API Requests?

When your component fetches data from an API, the request goes over the network and depends on:

  • The API’s availability.
  • The server’s response time.
  • The data format returned by the API.

In tests, these factors introduce instability.

For example, if an API goes down or changes its data structure, your tests may fail — even though there’s nothing wrong with your component logic.

By mocking API calls, you replace the real network request with a fake response that mimics the behavior of the real API. This allows you to test how your code reacts to various scenarios — such as successful responses, empty data, or errors — without ever leaving the local test environment.


How Jest Helps with Mocking

Jest provides built-in utilities for mocking modules and functions.

With jest.mock(), you can replace an entire dependency (like Axios or Fetch) with a mock version. You can then configure how that mock behaves during your test.

This lets you write predictable, isolated unit tests that don’t depend on real API calls.


Common Libraries Used for API Calls

Before mocking, it’s helpful to know which HTTP client your project uses. In React apps, developers often use:

  • Axios — A promise-based HTTP client.
  • Fetch API — The built-in browser API for network requests.

Jest can mock both of these easily, but we’ll focus mainly on mocking Axios, since it’s widely used and provides a clean Promise-based interface.


Setting Up Jest for Testing

Most React projects already come with Jest preconfigured (for example, when using Create React App).

If Jest isn’t installed, you can set it up manually:

npm install --save-dev jest @testing-library/react @testing-library/jest-dom axios

Add this to your package.json to enable Jest:

"scripts": {
  "test": "jest"
}

Now your project is ready for testing with Jest.


Using jest.mock() to Mock Axios

Jest allows you to mock entire modules using the jest.mock() function. When you call jest.mock('axios'), Jest replaces all Axios methods with mock functions.

You can then configure these mock functions to return specific data or throw errors, depending on the scenario you want to test.

Let’s look at an example.


Example 1: Mocking Axios for Successful API Calls

Here’s a basic test that mocks an Axios GET request to simulate a successful response.

// userService.js
import axios from 'axios';

export const fetchUser = async () => {
  const response = await axios.get('/api/user');
  return response.data;
};

Now, we’ll write a Jest test that mocks this API request.

// userService.test.js
import axios from 'axios';
import { fetchUser } from './userService';

jest.mock('axios');

test('fetches data successfully', async () => {
  axios.get.mockResolvedValue({ data: { name: 'John' } });

  const data = await fetchUser();

  expect(data.name).toBe('John');
});

Step-by-Step Explanation

  1. Import Axios and the Function to Test
    The test imports the real Axios library (which will be mocked) and the function that makes the API call.
  2. Mock Axios Using jest.mock('axios')
    This tells Jest to replace all Axios methods (get, post, etc.) with mock functions.
  3. Set the Mock Response
    We use axios.get.mockResolvedValue() to tell Jest what the mock should return when called. In this case: axios.get.mockResolvedValue({ data: { name: 'John' } }); means that any call to axios.get() will return a resolved Promise containing that data.
  4. Run the Function and Assert
    The test runs fetchUser() and checks that the returned data matches the expected mock value.

Mocking Multiple Calls

You can also simulate multiple API calls or responses in sequence using mockResolvedValueOnce().

axios.get
  .mockResolvedValueOnce({ data: { name: 'Alice' } })
  .mockResolvedValueOnce({ data: { name: 'Bob' } });

This means the first call will return “Alice” and the second “Bob.”
This is useful for components or functions that make multiple API calls during a single render cycle.


Example 2: Simulating API Errors

In real-world apps, not all requests succeed. Testing error-handling logic is just as important.

You can simulate failed requests by using mockRejectedValue().

import axios from 'axios';
import { fetchUser } from './userService';

jest.mock('axios');

test('handles API error gracefully', async () => {
  axios.get.mockRejectedValue(new Error('API error'));

  try {
await fetchUser();
} catch (error) {
expect(error.message).toBe('API error');
} });

Explanation

  • mockRejectedValue() tells Jest to simulate a rejected Promise (like a failed API call).
  • In this example, when fetchUser() calls axios.get(), it throws an error.
  • We use a try...catch block to assert that the correct error message is returned.

This helps ensure that your application properly handles API failures — such as by displaying error messages or retrying requests.


Mocking Axios in React Components

So far, we’ve tested plain functions. But React components also make API requests — often in useEffect() hooks or event handlers.

Let’s test a React component that fetches data when it mounts.

Example Component

// UserComponent.jsx
import React, { useEffect, useState } from 'react';
import axios from 'axios';

const UserComponent = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
axios.get('/api/user').then((response) => {
  setUser(response.data);
});
}, []); if (!user) return <p>Loading...</p>; return <h2>{user.name}</h2>; }; export default UserComponent;

Example Test

// UserComponent.test.js
import { render, screen, waitFor } from '@testing-library/react';
import axios from 'axios';
import UserComponent from './UserComponent';

jest.mock('axios');

test('renders user name after fetching data', async () => {
  axios.get.mockResolvedValue({ data: { name: 'John' } });

  render(<UserComponent />);

  const userElement = await waitFor(() => screen.getByText('John'));
  expect(userElement).toBeInTheDocument();
});

Explanation of This Test

  1. Mock Axios Response
    The test simulates a successful API response returning { name: 'John' }.
  2. Render the Component
    The component mounts and makes a mock API call.
  3. Wait for the Data
    We use waitFor() to wait until the component updates with the fetched data.
  4. Assert the Output
    Finally, we check that “John” appears in the rendered output.

This approach ensures that your component’s UI and logic are both tested, even though no real network calls are made.


Mocking API Calls with Fetch

If your project uses the native Fetch API instead of Axios, you can mock it too.

global.fetch = jest.fn(() =>
  Promise.resolve({
json: () =&gt; Promise.resolve({ name: 'John' }),
}) ); test('fetches data successfully with fetch', async () => { const response = await fetch('/api/user'); const data = await response.json(); expect(data.name).toBe('John'); });

And to simulate an error:

global.fetch = jest.fn(() =>
  Promise.reject(new Error('Network error'))
);

Resetting and Clearing Mocks

When running multiple tests in the same file, it’s good practice to reset mocks between tests.

This ensures that data from one test doesn’t leak into another.

beforeEach(() => {
  jest.clearAllMocks();
});

You can also use:

  • jest.resetAllMocks() – Completely resets all mock behavior and implementation.
  • jest.restoreAllMocks() – Restores original (non-mocked) functions.

Advanced: Creating Custom Axios Mock Files

For larger test suites, instead of mocking Axios inline in every test, you can create a reusable mock file.

Example: __mocks__/axios.js

export default {
  get: jest.fn(() => Promise.resolve({ data: {} })),
  post: jest.fn(() => Promise.resolve({ data: {} })),
};

Jest will automatically use this mock whenever a module imports Axios.

You can then customize responses within specific test cases using:

axios.get.mockResolvedValue({ data: { name: 'Alice' } });

This keeps your tests cleaner and more maintainable.


Testing Loading and Error States

React components often render different states based on API responses — loading, success, or error. Let’s see how to test them all.

Component Example

// UserStatus.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function UserStatus() {
  const [status, setStatus] = useState('loading');

  useEffect(() => {
axios
  .get('/api/user')
  .then(() =&gt; setStatus('success'))
  .catch(() =&gt; setStatus('error'));
}, []); return <p>{status}</p>; } export default UserStatus;

Test Example

// UserStatus.test.js
import { render, screen, waitFor } from '@testing-library/react';
import axios from 'axios';
import UserStatus from './UserStatus';

jest.mock('axios');

test('renders success when API call succeeds', async () => {
  axios.get.mockResolvedValue({ data: { id: 1 } });
  render(<UserStatus />);
  const element = await waitFor(() => screen.getByText('success'));
  expect(element).toBeInTheDocument();
});

test('renders error when API call fails', async () => {
  axios.get.mockRejectedValue(new Error('API error'));
  render(<UserStatus />);
  const element = await waitFor(() => screen.getByText('error'));
  expect(element).toBeInTheDocument();
});

This approach ensures that your component behaves correctly for every API outcome.


Best Practices for Mocking API Requests

  1. Mock External Services, Not Internal Logic
    Only mock network requests. Let your component’s local logic run as usual.
  2. Keep Mocks Close to Tests
    If you only need a mock once, define it in that test file.
  3. Avoid Hardcoding Too Much Data
    Use simple, representative mock data.
  4. Test Edge Cases
    Include tests for empty data, timeouts, and errors.
  5. Reset Mocks Between Tests
    Use jest.clearAllMocks() or beforeEach() to maintain test isolation.
  6. Use Descriptive Test Names
    Make it clear which scenario each test covers (e.g., “handles API errors gracefully”).
  7. Avoid Over-Mocking
    If you mock too much, your tests lose realism. Mock only where necessary.

Advantages of Mocking API Calls

  • No Real Network Calls – Speeds up test runs dramatically.
  • Controlled Responses – You define exactly what data the API returns.
  • Error Simulation – Easily test how your app handles failures.
  • Reproducibility – Tests produce the same results every time.
  • Improved Test Coverage – You can cover success, failure, and edge cases easily.

Limitations of Mocking

While mocking is powerful, it has some trade-offs:

  • Doesn’t Test Real APIs – Mocks can diverge from real-world behavior if the API changes.
  • Maintenance Overhead – Mock data must be updated when APIs evolve.
  • False Confidence – A passing test might still fail against the live API.

Comments

Leave a Reply

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