How to Mock useTransition Hook in React
Testing React components that utilize concurrent features can
sometimes be challenging due to the asynchronous nature of state
transitions. This article provides a straightforward guide on how to
mock the useTransition hook in React using Jest and React
Testing Library. You will learn how to mock the hook’s return values to
control the pending state and ensure your transition callbacks execute
synchronously during unit tests.
Why Mock useTransition?
The useTransition hook returns an array containing two
elements: a boolean flag (isPending) and a function to
start the transition (startTransition).
In a testing environment, the asynchronous scheduling of concurrent
rendering can lead to flaky tests or race conditions. Mocking
useTransition allows you to: * Force the transition
callback to execute synchronously. * Explicitly control the
isPending state to test loading UI states. * Isolate your
component’s business logic from React’s internal concurrent
scheduler.
Standard Synchronous Mocking
To mock useTransition so that it executes the transition
callback immediately, you can spy on the react module and
provide a custom implementation. This is the most common approach for
general testing.
Add the following mock setup at the top of your test file:
import React from 'react';
jest.spyOn(React, 'useTransition').mockReturnValue([
false, // isPending is false
(callback) => callback(), // startTransition executes the callback immediately
]);By immediately invoking the callback passed to the mock
startTransition function, your state updates execute
synchronously, making them easy to assert using standard React Testing
Library utilities.
Testing the Pending State
If you need to test how your UI behaves when a transition is actively
pending (i.e., when isPending is true), you
can temporarily override the mock return value within a specific test
block.
test('renders loading state during transition', () => {
// Mock useTransition to return isPending as true
jest.spyOn(React, 'useTransition').mockReturnValue([
true,
(callback) => callback()
]);
render(<MyComponent />);
// Assert that your loading spinner or pending UI is visible
expect(screen.getByText('Loading...')).toBeInTheDocument();
});Complete Example
Below is a complete implementation showing how to test a component
that uses useTransition to filter a list.
The Component
(FilterList.js)
import React, { useState, useTransition } from 'react';
export function FilterList({ items }) {
const [isPending, startTransition] = useTransition();
const [filterTerm, setFilterTerm] = useState('');
const handleChange = (e) => {
startTransition(() => {
setFilterTerm(e.target.value);
});
};
const filteredItems = items.filter(item => item.includes(filterTerm));
return (
<div>
<input type="text" onChange={handleChange} placeholder="Search..." />
{isPending && <p>Updating list...</p>}
<ul>
{filteredItems.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}The Test Suite
(FilterList.test.js)
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { FilterList } from './FilterList';
describe('FilterList Component', () => {
beforeEach(() => {
jest.restoreAllMocks();
});
test('updates filter term synchronously when useTransition is mocked', () => {
// Mock useTransition to run synchronously
jest.spyOn(React, 'useTransition').mockReturnValue([
false,
(callback) => callback(),
]);
const items = ['Apple', 'Banana', 'Cherry'];
render(<FilterList items={items} />);
const input = screen.getByPlaceholderText('Search...');
fireEvent.change(input, { target: { value: 'Banana' } });
// Assert that the list filtered immediately
expect(screen.getByText('Banana')).toBeInTheDocument();
expect(screen.queryByText('Apple')).not.toBeInTheDocument();
});
});