Hone logo
Hone
Problems

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:

  1. Implement a function fetchUserData(userId: number) that makes an HTTP GET request to a hypothetical API endpoint /api/users/{userId}.
  2. Write Jest tests for fetchUserData that mock the HTTP requests.
  3. Ensure the tests cover successful data retrieval and error handling scenarios.

Key requirements:

  • Use the fetch API (or a similar mechanism like axios if you prefer, but assume fetch for mocking).
  • Utilize Jest's mocking features (jest.fn(), jest.spyOn(), or jest.mock()) to intercept and control fetch calls.
  • 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 fetchUserData is called with a valid userId, 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), fetchUserData should 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 fetchUserData function should be implemented in TypeScript.
  • Your Jest tests should also be written in TypeScript.
  • You must use Jest to mock the fetch API.
  • Assume the API endpoint is /api/users/{userId}.
  • The simulated user data object will have at least id and name properties.

Notes

  • Consider how you will structure your project to easily mock fetch. You might place your fetchUserData function in a separate module and mock that module, or mock the global fetch directly.
  • The Response object from fetch has a json() method that returns a Promise. Ensure your mock correctly implements this.
  • Remember to use async/await and try...catch or .rejects with Jest's matchers for asynchronous error handling.
  • Think about using jest.restoreAllMocks() or jest.clearAllMocks() within beforeEach or afterEach hooks to ensure mocks are clean between tests.
Loading editor...
typescript