How to Test React Refs
Testing React refs is essential for ensuring your application correctly interacts with the DOM to manage focus, trigger animations, or integrate with third-party libraries. This article provides a direct, practical guide on how to test React refs using Jest and React Testing Library. You will learn the recommended patterns for testing DOM-bound refs, testing forwarded refs, and asserting on ref-based behaviors without exposing internal implementation details.
Test Ref Behavior (Focus Management)
The recommended way to test refs when using React Testing Library is to test the observable behavior rather than the ref implementation itself. If a ref is used to focus an input element, your test should assert that the input element receives focus.
The Component
import React, { useRef } from 'react';
export function FocusInput() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} data-testid="my-input" />
<button onClick={handleClick}>Focus Input</button>
</div>
);
}The Test
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { FocusInput } from './FocusInput';
test('focuses the input element when the button is clicked', async () => {
render(<FocusInput />);
const input = screen.getByTestId('my-input');
const button = screen.getByRole('button', { name: /focus input/i });
// Ensure the input is not focused initially
expect(input).not.toHaveFocus();
// Click the button to trigger the ref action
await userEvent.click(button);
// Assert that the input now has focus
expect(input).toHaveFocus();
});Testing Forwarded Refs
When building reusable UI components, you often need to forward refs
to underlying DOM elements using React.forwardRef. To test
this, you can pass a created ref from the test environment into your
component and assert that the ref is populated with the correct DOM
node.
The Component
import React, { forwardRef } from 'react';
export const CustomInput = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
CustomInput.displayName = 'CustomInput';The Test
import React, { createRef } from 'react';
import { render } from '@testing-library/react';
import { CustomInput } from './CustomInput';
test('correctly forwards the ref to the input element', () => {
const ref = createRef();
render(<CustomInput ref={ref} placeholder="Enter text" />);
// Assert that the ref current value is a DOM input element
expect(ref.current).not.toBeNull();
expect(ref.current.tagName).toBe('INPUT');
expect(ref.current.placeholder).toBe('Enter text');
});Mocking
useRef for Implementation Testing
In rare scenarios where you cannot rely on DOM side-effects (such as
integrating with external imperative DOM libraries), you can mock
useRef to spy on its current property.
Note: Use this approach sparingly, as it tests implementation details rather than user behavior.
The Component
import React, { useRef, useEffect } from 'react';
export function ChartComponent({ data }) {
const chartRef = useRef(null);
useEffect(() => {
if (chartRef.current) {
// Imperative library call
chartRef.current.initializeChart(data);
}
}, [data]);
return <div ref={chartRef} data-testid="chart-container" />;
}The Test
import React from 'react';
import { render } from '@testing-library/react';
import { ChartComponent } from './ChartComponent';
test('calls the imperative chart initialization on the ref', () => {
const mockInitializeChart = jest.fn();
// Create a mock ref object with the spy function attached
const mockRef = {
_current: null,
get current() {
return this._current;
},
set current(val) {
this._current = val;
if (val) {
val.initializeChart = mockInitializeChart;
}
}
};
// Spy on React.useRef and return our mock ref
jest.spyOn(React, 'useRef').mockReturnValue(mockRef);
render(<ChartComponent data={[10, 20, 30]} />);
// Assert that the imperative method was called on the ref
expect(mockInitializeChart).toHaveBeenCalledWith([10, 20, 30]);
});