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();
});