How to Test useRef Hook in React

Testing the useRef hook in React can be challenging because updating a ref’s current property does not trigger a component re-render. To test useRef effectively, you should focus on testing the user-visible behavior or side effects caused by the ref, rather than trying to inspect the ref’s internal state directly. This article explains how to test the two primary use cases of useRef—accessing DOM elements and storing mutable instance variables—using React Testing Library and Jest.

Testing DOM References

The most common use case for useRef is to interact with DOM elements directly, such as managing focus, text selection, or media playback. When testing DOM references, you should assert on the expected DOM side effect rather than the ref object itself.

Consider this FocusInput component that focuses an input field when a button is clicked:

import React, { useRef } from 'react';

function FocusInput() {
  const inputRef = useRef(null);
  const handleFocus = () => {
    inputRef.current.focus();
  };

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

export default FocusInput;

To test this component using React Testing Library, verify that the input element gains focus after the button is clicked:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import FocusInput from './FocusInput';

test('focuses the input element when the button is clicked', async () => {
  render(<FocusInput />);

  const input = screen.getByPlaceholderText('Enter text');
  const button = screen.getByRole('button', { name: /focus input/i });

  // Verify input is not focused initially
  expect(input).not.toHaveFocus();

  // Trigger the click event
  await userEvent.click(button);

  // Verify the ref successfully focused the input
  expect(input).toHaveFocus();
});

Testing Mutable Values (Instance Variables)

Another use case for useRef is to store mutable values that persist across renders without triggering a new render when they change (e.g., tracking timers, API request cancel tokens, or counter variables).

Consider this Timer component that tracks how many times a button has been clicked using a ref:

import React, { useRef } from 'react';

function ClickTracker() {
  const clickCount = useRef(0);

  const handleClick = () => {
    clickCount.current += 1;
    console.log(`Clicked ${clickCount.current} times`);
  };

  return <button onClick={handleClick}>Click Me</button>;
}

export default ClickTracker;

Because the value of clickCount.current is not rendered to the screen, you cannot query the DOM for it. Instead, you can spy on the side effect (in this case, console.log) to verify the ref is updating correctly:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ClickTracker from './ClickTracker';

test('increments the ref value on click without re-rendering', async () => {
  const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
  render(<ClickTracker />);

  const button = screen.getByRole('button', { name: /click me/i });

  await userEvent.click(button);
  expect(logSpy).toHaveBeenCalledWith('Clicked 1 times');

  await userEvent.click(button);
  expect(logSpy).toHaveBeenCalledWith('Clicked 2 times');

  logSpy.mockRestore();
});

Mocking useRef in Jest

If you need to isolate a component and control the value of the ref during a unit test, you can mock useRef directly using Jest. This approach is useful when the ref holds a complex third-party library instance or a DOM API that is difficult to simulate in a JSDOM environment.

import React from 'react';
import { render } from '@testing-library/react';
import MyComponent from './MyComponent';

test('mocks the useRef hook', () => {
  const mockRef = { current: { someMethod: jest.fn() } };
  
  jest.spyOn(React, 'useRef').mockReturnValue(mockRef);

  render(<MyComponent />);

  // Assertions related to the mocked ref interaction
  expect(mockRef.current.someMethod).toHaveBeenCalled();
});