Hone logo
Hone
Problems

Mocking Custom Hook Results in Jest

This challenge focuses on a common scenario in React development: testing components that utilize custom hooks. You'll learn how to effectively mock the return values of these custom hooks within your Jest tests to isolate component logic and ensure predictable test outcomes.

Problem Description

You are tasked with testing a React component called UserProfile which depends on a custom hook, useUserData. This hook fetches user data and provides it to the component. Your goal is to write Jest tests for the UserProfile component. Specifically, you need to mock the useUserData hook to return different predefined data scenarios (e.g., user data available, loading state, error state) without actually executing the hook's original implementation. This allows you to test how UserProfile renders and behaves under various conditions provided by the hook.

Key Requirements:

  • Create a Jest test suite for the UserProfile component.
  • Mock the useUserData custom hook.
  • Configure the mock to return specific values for different test cases.
  • Test the UserProfile component's rendering based on the mocked hook's return values.

Expected Behavior:

  • When the mocked useUserData hook returns loading state, the UserProfile should display a loading indicator.
  • When the mocked useUserData hook returns user data, the UserProfile should display the user's name and email.
  • When the mocked useUserData hook returns an error, the UserProfile should display an error message.

Edge Cases to Consider:

  • What happens if the useUserData hook returns null or undefined for the user data in a non-error, non-loading state?

Examples

Example 1: User Data Loaded

// Assume UserProfile.tsx and useUserData.ts exist

// Mock setup (in your test file)
jest.mock('./useUserData', () => ({
  useUserData: () => ({
    userData: { id: 1, name: 'Alice Smith', email: 'alice@example.com' },
    isLoading: false,
    error: null,
  }),
}));

// Component to test (UserProfile.tsx)
import React from 'react';
import useUserData from './useUserData';

interface User {
  id: number;
  name: string;
  email: string;
}

interface UseUserDataReturn {
  userData: User | null;
  isLoading: boolean;
  error: string | null;
}

const useUserData = (): UseUserDataReturn => {
  // Original implementation (not executed in test)
  // ... fetch logic ...
  return { userData: null, isLoading: true, error: null };
};

const UserProfile: React.FC = () => {
  const { userData, isLoading, error } = useUserData();

  if (isLoading) {
    return <div>Loading user data...</div>;
  }

  if (error) {
    return <div>Error: {error}</div>;
  }

  if (!userData) {
    return <div>No user data available.</div>;
  }

  return (
    <div>
      <h2>{userData.name}</h2>
      <p>{userData.email}</p>
    </div>
  );
};

export default UserProfile;

// Test case
import { render, screen } from '@testing-library/react';
import UserProfile from './UserProfile';

describe('UserProfile', () => {
  it('should display user information when data is available', () => {
    render(<UserProfile />);
    expect(screen.getByText('Alice Smith')).toBeInTheDocument();
    expect(screen.getByText('alice@example.com')).toBeInTheDocument();
    expect(screen.queryByText('Loading user data...')).not.toBeInTheDocument();
    expect(screen.queryByText(/Error:/)).not.toBeInTheDocument();
  });
});

Output: The UserProfile component should render and display:

<div>
  <h2>Alice Smith</h2>
  <p>alice@example.com</p>
</div>

Explanation: The jest.mock call replaces the actual useUserData hook with a mock function. In this specific test case, the mock is configured to return an object with userData populated, isLoading as false, and error as null. The UserProfile component then receives this data and renders the user's name and email.

Example 2: Loading State

// Mock setup
jest.mock('./useUserData', () => ({
  useUserData: () => ({
    userData: null,
    isLoading: true,
    error: null,
  }),
}));

// ... (UserProfile component remains the same as Example 1)

// Test case
import { render, screen } from '@testing-library/react';
import UserProfile from './UserProfile';

describe('UserProfile', () => {
  // ... other tests ...

  it('should display a loading indicator when data is loading', () => {
    render(<UserProfile />);
    expect(screen.getByText('Loading user data...')).toBeInTheDocument();
    expect(screen.queryByText('Alice Smith')).not.toBeInTheDocument();
    expect(screen.queryByText(/Error:/)).not.toBeInTheDocument();
  });
});

Output: The UserProfile component should render and display:

<div>Loading user data...</div>

Explanation: The mock useUserData hook is set to return isLoading: true. The UserProfile component's conditional rendering logic correctly identifies this and displays the loading message.

Example 3: Error State

// Mock setup
jest.mock('./useUserData', () => ({
  useUserData: () => ({
    userData: null,
    isLoading: false,
    error: 'Failed to fetch user data',
  }),
}));

// ... (UserProfile component remains the same as Example 1)

// Test case
import { render, screen } from '@testing-library/react';
import UserProfile from './UserProfile';

describe('UserProfile', () => {
  // ... other tests ...

  it('should display an error message when an error occurs', () => {
    render(<UserProfile />);
    expect(screen.getByText('Error: Failed to fetch user data')).toBeInTheDocument();
    expect(screen.queryByText('Alice Smith')).not.toBeInTheDocument();
    expect(screen.queryByText('Loading user data...')).not.toBeInTheDocument();
  });
});

Output: The UserProfile component should render and display:

<div>Error: Failed to fetch user data</div>

Explanation: The mock useUserData hook is set to return error: 'Failed to fetch user data'. The UserProfile component handles this by displaying the error message.

Constraints

  • Your solution should be written in TypeScript.
  • You must use Jest and @testing-library/react for testing.
  • The useUserData hook implementation itself should not be tested; only the UserProfile component's interaction with it.
  • Avoid importing the actual useUserData implementation into your test files.

Notes

  • The jest.mock function is your primary tool here. You'll use it to replace the module that exports useUserData.
  • Consider using jest.fn() within your mock to potentially track calls to the hook if needed for more advanced scenarios, though it's not strictly required for this challenge.
  • The goal is to test the component's behavior based on what the hook returns, not how the hook works internally.
  • Think about how to dynamically set the mock's return value for different test cases. The mockImplementation or mockReturnValue methods of Jest mocks can be very useful.
Loading editor...
typescript