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()orjest.mock()to create mock functions/modules. - The mock API client should have a method, for example,
fetchUserById(userId: string), that returns aPromiseresolving to aUserobject ornullif 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
nullis 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
fetchUserByIdmethod should return aPromisethat resolves. Do not simulate network latency withsetTimeoutin 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
apiClientwithin 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.