How to Mock useRef Hook in React

Mocking the useRef hook in React is a common requirement when unit testing components that interact directly with the DOM or hold mutable values. This article provides a straightforward guide on how to mock useRef using Jest and React Testing Library, demonstrating how to spy on the hook, control its .current property, and verify component behavior during testing.

Why Mock useRef?

In React, useRef is typically used to access DOM elements directly (like focusing an input, playing media, or calculating element dimensions) or to persist values across renders without triggering a re-render.

When writing unit tests, you might not want to render the full DOM or rely on actual browser APIs. Mocking useRef allows you to isolate the component’s logic by replacing the ref’s behavior with Jest mock functions.

The Component to Test

Consider a simple component that uses useRef to programmatically focus a text input when a button is clicked:

import React, { useRef } from 'react';

const FocusInput = () => {
  const inputRef = useRef(null);

  const handleFocus = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>Focus Input</button>
    </div>
  );
};

export default FocusInput;

How to Mock useRef Using jest.spyOn

The cleanest way to mock useRef is by using jest.spyOn on the React object. This allows you to override the default implementation of the hook and return a custom mock object for the duration of your test.

Here is how you can write the test:

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import FocusInput from './FocusInput';

describe('FocusInput Component', () => {
  it('should call the focus method on the input when the button is clicked', () => {
    // 1. Create a mock focus function
    const mockFocus = jest.fn();

    // 2. Spy on React.useRef and mock its return value
    jest.spyOn(React, 'useRef').mockReturnValue({
      current: {
        focus: mockFocus,
      },
    });

    // 3. Render the component
    render(<FocusInput />);

    // 4. Trigger the click event
    const button = screen.getByRole('button', { name: /focus input/i });
    fireEvent.click(button);

    // 5. Assert that the mocked focus function was called
    expect(mockFocus).toHaveBeenCalledTimes(1);
  });
});

Explanation of the Steps

  1. Create a Mock Function: We define mockFocus = jest.fn() to track whether the element’s focus method is successfully called.
  2. Spy on React.useRef: By using jest.spyOn(React, 'useRef'), we intercept React’s internal useRef implementation. We chain .mockReturnValue() to return a dummy object containing our mock function inside the current property.
  3. Render and Act: We render the component using React Testing Library and simulate a user clicking the button.
  4. Assert: Finally, we assert that mockFocus was called, verifying that the ref integration works as expected.

Clean Up After Tests

Because jest.spyOn modifies the behavior of the React object globally for the test file, it is important to restore the original implementation after your tests run. You can achieve this by adding jest.restoreAllMocks() in an afterEach block:

afterEach(() => {
  jest.restoreAllMocks();
});