Hone logo
Hone
Problems

Testing Asynchronous Operations with Jest Promises

This challenge focuses on writing robust tests for asynchronous JavaScript code using Promises in Jest. You will learn how to effectively assert the outcomes of Promises, including successful resolutions and rejections, ensuring your asynchronous logic behaves as expected.

Problem Description

You are tasked with writing unit tests for a hypothetical userService that interacts with an external API to fetch user data. This service uses Promises to handle asynchronous operations. Your goal is to write Jest tests that verify the correct behavior of the userService in various scenarios, including successful data retrieval and API errors.

Key Requirements:

  1. Test Successful Resolution: Write a test to ensure that when the fetchUser function successfully resolves with user data, the Promise is correctly handled and the expected data is returned.
  2. Test Rejection: Write a test to verify that when the fetchUser function encounters an error (e.g., API unavailable, user not found), the Promise is correctly rejected with an appropriate error message.
  3. Mocking API Calls: Utilize Jest's mocking capabilities to simulate the behavior of the underlying API calls, allowing you to control the outcomes (resolve or reject) without making actual network requests.

Expected Behavior:

  • Your tests should accurately reflect whether the fetchUser Promise resolves with the correct user object or rejects with a specific error.
  • The tests should be independent and not rely on external services.

Edge Cases to Consider:

  • What happens if the API returns an unexpected data format? (While not explicitly tested here, it's good to keep in mind for real-world scenarios).
  • Handling different types of errors returned by the API.

Examples

Let's assume we have a userService module with the following structure (for illustrative purposes, you don't need to implement this, just test it):

// userService.ts (Hypothetical)
interface User {
  id: number;
  name: string;
}

const mockApi = {
  get: async (url: string): Promise<User> => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (url === '/api/users/1') {
          resolve({ id: 1, name: 'Alice' });
        } else {
          reject(new Error('User not found'));
        }
      }, 100);
    });
  },
};

export const userService = {
  fetchUser: async (userId: number): Promise<User> => {
    try {
      const user = await mockApi.get(`/api/users/${userId}`);
      return user;
    } catch (error) {
      throw error; // Re-throw the error to be caught by the caller
    }
  },
};

Example Test Scenario 1: Successful User Fetch

Input (Implicit): userService.fetchUser(1) is called. The mocked API resolves with { id: 1, name: 'Alice' }.

Expected Output: The fetchUser Promise resolves with an object { id: 1, name: 'Alice' }.

Explanation: The test should assert that when fetchUser is called with a valid userId, the returned Promise resolves with the expected user data.

Example Test Scenario 2: User Not Found

Input (Implicit): userService.fetchUser(99) is called. The mocked API rejects with new Error('User not found').

Expected Output: The fetchUser Promise rejects with an error whose message is 'User not found'.

Explanation: The test should assert that when fetchUser is called with an invalid userId, the returned Promise rejects with the expected error.

Constraints

  • Your tests must be written in TypeScript.
  • You must use Jest for your testing framework.
  • Do not make actual HTTP requests. All API interactions should be mocked.
  • The solution should demonstrate clear and concise testing of Promise resolutions and rejections.

Notes

  • To mock the mockApi object, you will likely need to use jest.mock() and jest.spyOn().
  • When testing for Promise rejections, Jest provides specific matchers like .rejects.toThrow() or .rejects.toMatchObject().
  • Consider using async/await within your test functions for cleaner Promise handling.
  • The focus is on testing the asynchronous behavior of userService.fetchUser.
Loading editor...
typescript