When writing tests in Jest, relying on live API calls often leads to flaky results. Data in the backend might change unexpectedly, network calls can take too long, and staging environments are not always set up with the exact data you need.
Mocking API calls in Jest solves these issues by letting you define the responses your code will receive. This gives you full control over scenarios like empty datasets, error responses, or slow requests without touching the real API.
This article explains what mock API calls in Jest are, why they are used, and how to set them up for both straightforward and complex test cases.
What Are Mock API Calls in Jest?
Mock API calls in Jest are fake network requests used in tests so they don’t rely on a live API. They allow developers to replace the actual API call logic with predefined responses or behaviors. This ensures the test environment is predictable and independent from external systems.
A mock API call can be as simple as returning a static JSON object or as complex as simulating multiple conditional responses. In Jest, this is typically achieved using jest.mock() to override a module or by creating manual mock files. By doing this, test execution becomes faster and more reliable, and results do not vary due to backend changes or network issues.
Why Use Mock API Calls in Jest for Testing?
Mocking API calls in Jest solves specific challenges that appear when tests depend on live services. Without mocking, test outcomes can vary due to changing data, network delays, or incomplete backend setups.
Below are the key reasons to use mock API calls in Jest:
- Reproducible test conditions: Tests always run against the same input and output, which makes debugging easier and ensures results do not change between runs
- Greater coverage of edge cases: Rare scenarios like specific error codes or empty payloads can be created on demand without waiting for them to occur in the real API
- Faster feedback during development: Tests run quickly because the network layer is removed, allowing more frequent runs without slowing down the workflow
Also Read: How to improve DevOps Feedback Loop
- Clear separation of concerns: Application logic is tested in isolation from the actual API, which helps identify whether failures are due to code issues or external dependencies
- Reduced reliance on external environments: Work can continue even if staging servers are offline or incomplete, preventing development delays
Getting Started: Project Setup for Mock API Testing
Before writing tests, the project needs a proper setup so Jest can run and handle mocked API calls correctly. This involves installing the required packages, configuring Jest to work with mocks, and preparing a folder structure for mock data and fixtures.
1. Installing Jest and Dependencies
Jest can be installed in most JavaScript projects using npm or yarn. For projects that rely on APIs, it is also useful to install libraries for making HTTP requests, such as axios or node-fetch, so the mock targets are clear.
npm install --save-dev jest npm install axios
2. Configuring Jest for API Mocking
Jest requires configuration so it can locate mock files and handle them in place of the real modules. This is usually done in the jest.config.js file by setting up the moduleNameMapper or specifying where Jest should look for manual mocks.
// jest.config.js module.exports = { testEnvironment: 'node', moduleNameMapper: { '^axios$': '<rootDir>/__mocks__/axios.js' } };
Read More: How to Configure Jest
3. Folder Structure for Mock Data and Fixtures
A consistent folder structure helps keep mock files organized. Below is one example:
project-root/ __mocks__/ // Contains manual mocks for modules like axios tests/ // Contains test files fixtures/ // Contains static JSON or mock data
Organizing files in this way makes it easier to locate and update mocks without affecting unrelated tests.
Step-by-Step: Writing Your First Mock API Test in Jest
Once the setup is complete, the first mock API test can be created. This involves defining a function that makes an API call, replacing it with a mock, and verifying the result.
1. Creating a Simple API Function
This is the function under test. It fetches data from an API and returns it.
// api.js const axios = require('axios'); async function getUser(id) { const response = await axios.get(`/users/${id}`); return response.data; } module.exports = getUser;
2. Using jest.mock() to Replace API Calls
In the test file, the real axios module is replaced with a mock so the request never leaves the test environment.
// api.test.js const axios = require('axios'); const getUser = require('./api'); jest.mock('axios'); test('should return mock user data', async () => { axios.get.mockResolvedValue({ data: { id: 1, name: 'John Doe' } }); const result = await getUser(1); expect(result).toEqual({ id: 1, name: 'John Doe' }); });
3. Adding Static JSON Responses
Instead of hardcoding data in the test, a JSON fixture can be imported. This makes the test more maintainable.
// fixtures/user.json { "id": 1, "name": "John Doe" } // api.test.js const userFixture = require('../fixtures/user.json'); axios.get.mockResolvedValue({ data: userFixture });
4. Running and Validating the Test
The test is run with the standard Jest command. If the configuration and mock are correct, the test should pass and confirm that the function returns the expected data.
npm test
Expanding to Real-World API Mocking Scenarios
Mocking a single response is useful for basic tests, but production APIs often involve more complex behaviors. Jest can handle these cases by customizing mock implementations to replicate real usage patterns. Below are practical examples that address these real-world API mocking scenarios.
1. Mocking APIs with Pagination
APIs that return paginated data need tests for multiple page requests. The mock should return different data depending on the requested page.
axios.get.mockImplementation((url) => { if (url.includes('page=1')) { return Promise.resolve({ data: { users: ['User A', 'User B'], nextPage: 2 } }); } if (url.includes('page=2')) { return Promise.resolve({ data: { users: ['User C', 'User D'], nextPage: null } }); } });
This approach ensures the pagination logic in the application is validated without relying on the backend to provide staged data.
2. Testing Conditional Responses Based on Request Parameters
Some APIs return different data based on filters, query parameters, or headers. Mocks can be set up to return specific results that match each condition.
axios.get.mockImplementation((url) => { if (url.includes('role=admin')) { return Promise.resolve({ data: { users: ['Admin 1', 'Admin 2'] } }); } return Promise.resolve({ data: { users: ['User 1', 'User 2'] } }); });
This makes it possible to test how the application handles role-based views, filters, and permissions without altering backend configurations.
3. Simulating Slow Networks and Timeouts
Network delays and timeouts are common in real usage. These scenarios can be tested by using timers or rejected promises.
axios.get.mockImplementation(() => new Promise((resolve) => { setTimeout(() => resolve({ data: { status: 'delayed' } }), 3000); }) );
To simulate a timeout:
axios.get.mockRejectedValue(new Error('Timeout error'));
Testing these scenarios ensures the UI or logic responds correctly to performance issues.
4. Chaining Dependent API Calls in a Workflow
Some workflows make one API call and use its response in a subsequent request. Mocks can simulate the chain by validating the sequence of calls.
axios.get .mockResolvedValueOnce({ data: { userId: 10 } }) // First API call .mockResolvedValueOnce({ data: { orders: ['Order 1', 'Order 2'] } }); // Second API call
This lets the test confirm that the second call uses data from the first and that the application handles the workflow correctly.
5. Simulating Random or Intermittent Failures
In real APIs, not all errors are consistent. Sometimes requests succeed and fail intermittently. Mocks can mimic this to test retry strategies and error handling.
Example:
let callCount = 0; axios.get.mockImplementation(() => { callCount++; if (callCount % 2 === 0) { return Promise.resolve({ data: { message: 'success' } }); } return Promise.reject(new Error('Temporary error')); });
6. Simulating API Version Changes
When an API endpoint is updated or a new version is introduced, the mock can replicate the older and newer payload formats to verify backward compatibility.
Example:
axios.get.mockImplementation((url) => { if (url.includes('v1')) { return Promise.resolve({ data: { name: 'John Doe' } }); } if (url.includes('v2')) { return Promise.resolve({ data: { firstName: 'John', lastName: 'Doe' } }); } });
Debugging Mock API Calls in Jest
Even with a correct setup, mocked API calls in Jest can fail for reasons that are not obvious at first glance. Here are some common issues and how to fix them.
1. Checking if Mocks Are Called Correctly
One of the most common testing issues is when a mock function is never called or is called with the wrong arguments. This usually means either the code under test never triggered the expected API call, or the mock was not applied to the right module instance.
Jest includes built-in matchers that help verify both the number of calls and the exact arguments used. This ensures your test is not only running but also interacting with the mock in the intended way.
expect(axios.get).toHaveBeenCalled(); expect(axios.get).toHaveBeenCalledWith('/users/1');
- toHaveBeenCalled(): Confirms that the mock function was invoked at least once.
- toHaveBeenCalledWith(): Validates that the mock was called with the exact arguments you expect.
Also Read: How to Use toHaveBeenCalledWith in Jest
2. Fixing Async and Await Test Failures
When working with async code, tests may complete before the mocked API response is resolved. This can cause false positives or unexpected failures. The example below uses async/await to ensure that the test waits for the mocked response before performing assertions.
test('fetches user', async () => { axios.get.mockResolvedValue({ data: { id: 1, name: 'John' } }); const result = await getUser(1); expect(result).toEqual({ id: 1, name: 'John' }); });
For multiple async operations, await Promise.all() can ensure all mocks have been resolved before assertions are made.
Also Read: it.each function in Jest
3. Avoiding Conflicts Between Multiple Mocks
When tests use multiple mocked modules or multiple test cases modify the same mock, responses can leak from one test to another. The example below clears all mock calls and instances before each test to avoid such interference.
beforeEach(() => { jest.clearAllMocks(); });
If the test suite uses different mock implementations for the same function, use mockReset() instead to remove any assigned behavior before redefining it.
4. Tracing Mock Implementations During Test Runs
If a test fails due to unexpected mock behavior, logging mock calls can help identify what was received and returned. The example below prints all recorded calls to axios.get, so the test output shows the arguments used.
console.log(axios.get.mock.calls);
This is useful for identifying whether the function was called with the expected arguments or if the wrong data path was used in the application logic.
Best Practices for Maintaining Mock API Calls in Jest
As test suites grow, managing mocked API calls can become challenging. Without proper practices, mocks may become inconsistent, hard to locate, or outdated compared to the real API. The goal is to ensure mocks remain accurate, easy to update, and reliable across the codebase.
1. Keep Mock Data in a Central Location
When mock data is scattered across multiple tests, changes to the API require updating several files, which increases the chance of missed updates. Storing all mock data and fixtures in a dedicated folder ensures a single source of truth and makes it easier for the team to locate and reuse data.
project-root/ __mocks__/ axios.js fixtures/ user.json orders.json tests/
This structure helps avoid duplication and ensures that changes to mock data are made in one place.
2. Match Mocks with Real API Contracts
If a mock response does not match the current API schema, tests can pass even though the application would fail against the real API. Keeping mocks aligned with the actual contract ensures that tests remain a reliable reflection of production behavior.
// Updated to match API returning firstName and lastName instead of name axios.get.mockResolvedValue({ data: { firstName: 'John', lastName: 'Doe' } });
This prevents false test passes that occur when mocks no longer reflect actual API responses.
3. Use Fixtures for Complex or Repeated Responses
Large mock objects written directly in test files can make them hard to read and maintain. Moving repeated or complex data into fixtures makes tests more concise, easier to scan, and less prone to copy-paste errors.
const userFixture = require('../fixtures/user.json'); axios.get.mockResolvedValue({ data: userFixture });
Fixtures keep tests concise and allow multiple test cases to use the same data source.
4. Reset Mocks Between Tests
When mocks are not cleared between tests, state can leak from one test to another, leading to unpredictable results. Resetting mocks before each test run ensures isolation and guarantees that each test starts with a clean state.
beforeEach(() => { jest.clearAllMocks(); });
This avoids scenarios where one test unintentionally depends on the behavior of a previous test.
5. Document Mock Behavior for Team Members
Add short comments in the test or mock file to explain what a particular mock is meant to simulate. Without this context, developers who did not write the test may misinterpret the purpose of the mock or assume it represents the real API behavior. This can lead to incorrect changes or the removal of important test scenarios.
// Simulating a 404 error for a non-existent user axios.get.mockRejectedValue({ response: { status: 404 } });
Clear documentation helps maintain consistency in how mocks are created and ensures that every team member understands why a specific response or error is included.
How Requestly Simplifies Mock API Calls in Jest
Managing API mocks inside Jest works well for isolated unit tests, but it can get harder to maintain when:
- You have multiple services interacting with each other
- You need to test in a real browser or end-to-end environment
- Mock data needs to be updated without redeploying the test code
Requestly by BrowserStack addresses these challenges by letting you intercept and modify API calls at the network level, without changing your Jest tests or application code.
1. Create and Update Mocks Without Code Changes
In Jest, updating a mock requires modifying the test or fixture files, which means a new commit and possibly a CI run. With Requestly, you can configure mock responses through its interface and apply them instantly. This makes it faster to experiment with different responses or error scenarios without waiting for code changes to deploy.
2. Simulate Realistic Network Conditions
Jest mocks bypass the network entirely, so they can’t simulate latency, connection drops, or partial responses. Requestly can introduce artificial delays or failures, letting you test how the UI behaves under real-world conditions that code-level mocks can’t replicate.
3. Keep Mocks Consistent Across Testing Environments
When mocks are defined only in Jest, other testing environments, such as Cypress or manual QA in a staging build, do not benefit from them. Requestly applies the same mock rules across browsers, end-to-end tests, and local environments, ensuring consistency between different stages of testing.
4. Share Mock Configurations With the Team
In larger teams, it’s common for each developer to create their own mocks, which can lead to inconsistent results. Requestly allows mock rules to be shared, so everyone tests against the same scenarios without manually syncing files. This reduces “works on my machine” issues and speeds up debugging.
Conclusion
Mocking API calls in Jest is a crucial step in building fast, reliable, and independent unit tests. By isolating your tests from external dependencies, you reduce flakiness, speed up execution, and make it easier to simulate different scenarios such as errors, slow responses, or specific payloads. This ensures that tests reflect the true behavior of your code without being affected by unstable or unavailable backend services.
Requestly can further streamline this process by allowing you to intercept, modify, and simulate API responses directly in the browser or across different environments. This eliminates the need to hardcode mock data in every test file and makes it possible to share consistent mock setups with your team.