Mocking HTTP Request Handlers in Jest for API Integration Testing
Testing components that interact with external APIs can be challenging due to their asynchronous nature and potential unreliability. This challenge focuses on using Jest to effectively mock HTTP request handlers, allowing you to isolate your code and test its behavior in a controlled environment. You will implement a function that fetches data from a simulated API and write Jest tests to verify its functionality without making actual network requests.
Problem Description
Your task is to create a function that simulates fetching data from a web API. You will then write Jest tests for this function. Instead of making real HTTP requests, you will use Jest's mocking capabilities to intercept these requests and provide predefined responses. This approach is crucial for ensuring the reliability, speed, and determinism of your tests.
What needs to be achieved:
- Implement a function
fetchUserData(userId: number)that makes an HTTP GET request to a hypothetical API endpoint/api/users/{userId}. - Write Jest tests for
fetchUserDatathat mock the HTTP requests. - Ensure the tests cover successful data retrieval and error handling scenarios.
Key requirements:
- Use the
fetchAPI (or a similar mechanism likeaxiosif you prefer, but assumefetchfor mocking). - Utilize Jest's mocking features (
jest.fn(),jest.spyOn(), orjest.mock()) to intercept and controlfetchcalls. - The tests should verify that the correct URL is requested and that the function correctly processes both successful responses and network errors.
Expected behavior:
- When
fetchUserDatais called with a validuserId, it should return a Promise that resolves with the user data (simulated as a JavaScript object). - When the API request fails (e.g., network error, server error status code),
fetchUserDatashould return a Promise that rejects with an appropriate error.
Edge cases to consider:
- API returns a non-OK status code (e.g., 404, 500).
- Network connectivity issues.
Examples
Example 1: Successful Data Fetch
// Assume a module 'apiService.ts' contains the fetchUserData function
// and 'jest.mock()' is used to mock the fetch implementation globally.
// --- In apiService.ts ---
// async function fetchUserData(userId: number): Promise<any> {
// const response = await fetch(`/api/users/${userId}`);
// if (!response.ok) {
// throw new Error(`HTTP error! status: ${response.status}`);
// }
// return response.json();
// }
// --- In apiService.test.ts ---
// Mock the global fetch
// global.fetch = jest.fn();
// ... inside describe block ...
// it('should fetch user data successfully', async () => {
// const mockUserData = { id: 1, name: 'Alice' };
// const mockResponse = {
// ok: true,
// json: async () => mockUserData,
// };
// global.fetch.mockResolvedValue(mockResponse); // Mocking the successful fetch
// const userId = 1;
// const userData = await fetchUserData(userId);
// expect(global.fetch).toHaveBeenCalledTimes(1);
// expect(global.fetch).toHaveBeenCalledWith(`/api/users/${userId}`);
// expect(userData).toEqual(mockUserData);
// });
Input: userId = 1
Expected fetchUserData return value: A Promise resolving to { id: 1, name: 'Alice' }
Explanation: The mock fetch is set up to resolve with a successful response, simulating the API returning user data. The test verifies that fetch was called with the correct URL and that the function returned the JSON data.
Example 2: API Returns Error Status
// --- In apiService.test.ts ---
// Mock the global fetch
// global.fetch = jest.fn();
// ... inside describe block ...
// it('should throw an error for non-existent user', async () => {
// const mockResponse = {
// ok: false,
// status: 404,
// statusText: 'Not Found',
// };
// global.fetch.mockResolvedValue(mockResponse); // Mocking a failed fetch
// const userId = 999;
// await expect(fetchUserData(userId)).rejects.toThrow('HTTP error! status: 404');
// expect(global.fetch).toHaveBeenCalledTimes(1);
// expect(global.fetch).toHaveBeenCalledWith(`/api/users/${userId}`);
// });
Input: userId = 999 (simulating a user that doesn't exist)
Expected fetchUserData return value: A Promise rejecting with an Error object containing "HTTP error! status: 404".
Explanation: The mock fetch is configured to return a response with ok: false and a status: 404. The test asserts that calling fetchUserData with this mocked fetch will result in a rejected Promise with the expected error message.
Example 3: Network Error During Fetch
// --- In apiService.test.ts ---
// Mock the global fetch
// global.fetch = jest.fn();
// ... inside describe block ...
// it('should throw an error on network failure', async () => {
// const networkError = new Error('Failed to connect');
// global.fetch.mockRejectedValue(networkError); // Mocking a network error
// const userId = 2;
// await expect(fetchUserData(userId)).rejects.toThrow('Failed to connect');
// expect(global.fetch).toHaveBeenCalledTimes(1);
// expect(global.fetch).toHaveBeenCalledWith(`/api/users/${userId}`);
// });
Input: userId = 2
Expected fetchUserData return value: A Promise rejecting with an Error object containing "Failed to connect".
Explanation: The mock fetch is set to reject with a simulated network error. The test verifies that the fetchUserData function correctly propagates this error by rejecting its own Promise.
Constraints
- Your
fetchUserDatafunction should be implemented in TypeScript. - Your Jest tests should also be written in TypeScript.
- You must use Jest to mock the
fetchAPI. - Assume the API endpoint is
/api/users/{userId}. - The simulated user data object will have at least
idandnameproperties.
Notes
- Consider how you will structure your project to easily mock
fetch. You might place yourfetchUserDatafunction in a separate module and mock that module, or mock the globalfetchdirectly. - The
Responseobject fromfetchhas ajson()method that returns a Promise. Ensure your mock correctly implements this. - Remember to use
async/awaitandtry...catchor.rejectswith Jest's matchers for asynchronous error handling. - Think about using
jest.restoreAllMocks()orjest.clearAllMocks()withinbeforeEachorafterEachhooks to ensure mocks are clean between tests.