Hone logo
Hone
Problems

Mastering Jest mocks for Robust Unit Testing

In software development, it's crucial to isolate the code you're testing. Often, this involves external dependencies like APIs, databases, or third-party libraries. Jest's __mocks__ folder provides a powerful mechanism to create mock implementations of these dependencies, allowing you to test your code in isolation and with predictable behavior. This challenge will guide you through setting up and utilizing the __mocks__ folder to achieve this.

Problem Description

Your task is to refactor an existing TypeScript project to effectively use Jest's __mocks__ directory for dependency mocking. You will be provided with a small application that has a service responsible for fetching data from an external API. Your goal is to write unit tests for this service without actually making network requests.

What needs to be achieved:

  1. Create a mock implementation for an external API client.
  2. Configure Jest to automatically use this mock implementation when the real module is imported.
  3. Write unit tests for the data fetching service that leverage the mock API client.

Key requirements:

  • The mock should accurately simulate the behavior of the real API client, including returning specific data or throwing errors.
  • Tests should pass when running Jest with the mocks in place.
  • No actual network requests should be made during testing.

Expected behavior:

  • When the fetchData function in your service is called, it should use the mocked API client.
  • The mock API client should return predefined data for successful requests and simulate error conditions.
  • Your unit tests should verify that the fetchData function correctly processes the data or handles errors returned by the mock.

Important edge cases to consider:

  • Simulating API errors (e.g., network issues, server errors).
  • Mocking different API response structures.

Examples

Let's assume we have a service dataService.ts that depends on an apiClient.ts.

apiClient.ts (Original, to be mocked):

// This is a placeholder for an actual API client
// It might make network requests
export const apiClient = {
  get: async (url: string): Promise<any> => {
    console.log(`Making real API call to: ${url}`); // This should NOT appear in test logs
    // In a real scenario, this would use fetch or axios
    return fetch(url).then(res => res.json());
  }
};

dataService.ts:

import { apiClient } from './apiClient';

export const fetchData = async (userId: number): Promise<any> => {
  try {
    const response = await apiClient.get(`/users/${userId}`);
    return { success: true, data: response };
  } catch (error) {
    console.error("Error fetching data:", error);
    return { success: false, error: 'Failed to fetch user data' };
  }
};

Example 1: Successful Data Fetch

Input (for the test): Calling fetchData(1)

Mock Configuration: Create __mocks__/apiClient.ts that exports a mock apiClient.

// __mocks__/apiClient.ts
export const apiClient = {
  get: jest.fn().mockResolvedValue({ id: 1, name: 'John Doe' }),
};

Expected Output (from fetchData(1)):

{
  "success": true,
  "data": {
    "id": 1,
    "name": "John Doe"
  }
}

Explanation: When fetchData(1) is called, Jest intercepts the import { apiClient } from './apiClient' statement and uses the mock implementation from __mocks__/apiClient.ts. The mocked apiClient.get is called with /users/1 and is configured to resolve with the { id: 1, name: 'John Doe' } object. The fetchData function then returns this data wrapped in a success: true object.

Example 2: API Error

Input (for the test): Calling fetchData(2)

Mock Configuration: Modify __mocks__/apiClient.ts to simulate an error.

// __mocks__/apiClient.ts
export const apiClient = {
  get: jest.fn().mockRejectedValue(new Error('Network Error')),
};

Expected Output (from fetchData(2)):

{
  "success": false,
  "error": "Failed to fetch user data"
}

Explanation: In this scenario, the mocked apiClient.get is configured to reject with a Network Error. The fetchData function's catch block catches this error, logs it (which we can ignore in assertions for this problem), and returns a success: false object with an error message.

Constraints

  • Your project will have a src directory containing apiClient.ts and dataService.ts.
  • Your test file will be named dataService.test.ts.
  • You are expected to use Jest's auto-mocking feature by placing your mock in the __mocks__ directory at the same level as apiClient.ts (or in a top-level __mocks__ if preferred, though module-level is more common for specific dependencies).
  • The solution should be written in TypeScript.

Notes

  • Consider the jest.mock() API. You'll likely want to use it at the top of your test file to tell Jest to use the mock for the apiClient module.
  • Pay close attention to the pathing for your jest.mock() call and the __mocks__ directory.
  • When testing, use Jest's assertion methods to check the return value of fetchData and whether the mock apiClient.get was called with the correct arguments.
  • This challenge focuses on module-level mocking. For more advanced scenarios (like mocking classes or functions directly), you might explore other Jest mocking strategies, but for this problem, __mocks__ is the target.
Loading editor...
typescript