Hone logo
Hone
Problems

Mocking External Dependencies in Jest

Testing modern applications often involves interacting with external services or modules. Directly testing these interactions can be slow, unreliable, or even impossible. This challenge focuses on using Jest's mocking capabilities to replace these external dependencies with controlled "fake" objects, allowing for isolated and efficient unit testing.

Problem Description

You are tasked with testing a function that relies on an external API client. This API client is responsible for fetching user data from a remote server. Your goal is to write a unit test for this function without actually making any network requests.

You will need to:

  • Create a mock implementation of the API client.
  • Configure the mock to return specific data for a given user ID.
  • Ensure your test verifies that the function under test correctly uses the mocked API client and processes its response.

Key Requirements:

  • Use Jest's jest.fn() or jest.mock() to create mock functions/modules.
  • The mock API client should have a method, for example, fetchUserById(userId: string), that returns a Promise resolving to a User object or null if not found.
  • Your test should assert that the function under test calls the mock method with the correct arguments.
  • Your test should assert that the function under test correctly handles the resolved value from the mock.

Expected Behavior:

When the function under test is called with a user ID, it should invoke the mocked fetchUserById method with that user ID. If the mock resolves with user data, the function should return that data. If the mock resolves with null, the function should return null.

Edge Cases:

  • Consider a user ID that does not exist (mock returns null).
  • Consider scenarios where the mock might be configured to throw an error (though for this challenge, resolving with data or null is sufficient for the primary goal).

Examples

Example 1: Fetching an existing user

Assume you have a module userService.ts with a function getUserDetails(userId: string) that uses an apiClient to fetch user data.

// api.ts (external dependency)
export interface User {
  id: string;
  name: string;
  email: string;
}

export const apiClient = {
  fetchUserById: (userId: string): Promise<User | null> => {
    // In a real scenario, this would make a network request
    console.log(`Fetching user ${userId} from real API...`);
    return new Promise((resolve) => {
      setTimeout(() => {
        if (userId === "user-123") {
          resolve({ id: "user-123", name: "Alice", email: "alice@example.com" });
        } else {
          resolve(null);
        }
      }, 100);
    });
  },
};

// userService.ts
import { apiClient, User } from "./api";

export const getUserDetails = async (userId: string): Promise<User | null> => {
  const user = await apiClient.fetchUserById(userId);
  return user;
};

Test Case:

Input to getUserDetails: "user-123"

Expected output from getUserDetails: { id: "user-123", name: "Alice", email: "alice@example.com" }

Explanation: The test should mock apiClient.fetchUserById to return a Promise resolving with the specified user data when called with "user-123". The test should then assert that getUserDetails("user-123") resolves to the same user data.

Example 2: Fetching a non-existent user

Input to getUserDetails: "user-456"

Expected output from getUserDetails: null

Explanation: The test should mock apiClient.fetchUserById to return a Promise resolving with null when called with "user-456". The test should then assert that getUserDetails("user-456") resolves to null.

Constraints

  • Your solution must be written in TypeScript.
  • You must use Jest for testing.
  • The mocked fetchUserById method should return a Promise that resolves. Do not simulate network latency with setTimeout in your mock implementation for this challenge, but be aware that real-world mocks might need to simulate asynchronous behavior.

Notes

  • Consider how you will import and mock the apiClient within your test file. jest.mock() is a powerful tool for this.
  • You can use jest.spyOn() to mock specific methods on an existing module or object if you prefer not to mock the entire module.
  • When mocking a function that returns a Promise, use .mockResolvedValue() or .mockResolvedValueOnce() to set the resolved value.
Loading editor...
typescript