How to Mock useSyncExternalStore in Jest
This article explains how to mock React’s
useSyncExternalStore hook in your unit tests using testing
frameworks like Jest or Vitest. You will learn how to spy on the React
module to control the hook’s return value directly, as well as
alternative strategies like mocking the custom hooks or stores that wrap
this API for cleaner and more maintainable test suites.
Understanding the Challenge
The useSyncExternalStore hook is designed to subscribe
to external data sources in React. It accepts a subscription function
and a getSnapshot function to retrieve the current state.
When writing unit tests for components that rely on this hook, setting
up the actual external store can introduce unwanted complexity. Mocking
the hook allows you to isolate your component and test its behavior
under different state scenarios easily.
Approach 1: Mocking with jest.spyOn (Direct Mocking)
The most direct way to mock useSyncExternalStore is to
spy on the React module. This method is highly effective when you want
to control the returned state directly inside your individual test
cases.
Here is how you can implement it:
import React from 'react';
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
let useSyncExternalStoreSpy;
beforeEach(() => {
// Create a spy on the React hook
useSyncExternalStoreSpy = jest.spyOn(React, 'useSyncExternalStore');
});
afterEach(() => {
// Restore the original implementation after each test
useSyncExternalStoreSpy.mockRestore();
});
it('should render the mocked store state', () => {
// Force the hook to return your test data
useSyncExternalStoreSpy.mockReturnValue({
user: 'John Doe',
isLoggedIn: true,
});
render(<MyComponent />);
expect(screen.getByText('Welcome, John Doe')).toBeInTheDocument();
});
it('should render the logged-out state', () => {
useSyncExternalStoreSpy.mockReturnValue({
user: null,
isLoggedIn: false,
});
render(<MyComponent />);
expect(screen.getByText('Please log in')).toBeInTheDocument();
});
});Approach 2: Mocking the Custom Hook Wrapper (Recommended)
In production environments, useSyncExternalStore is
rarely used directly inside UI components. Instead, it is usually
wrapped inside a custom hook (such as useAuth or
useStore).
Mocking the custom hook rather than the low-level React API is generally considered a better practice. It keeps your tests decoupled from React’s internal implementation.
Suppose you have a custom hook file named
useMyStore.js:
// useMyStore.js
import { useSyncExternalStore } from 'react';
import { myStore } from './store';
export function useMyStore() {
return useSyncExternalStore(myStore.subscribe, myStore.getSnapshot);
}You can mock this custom hook in your test file using
jest.mock:
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
import { useMyStore } from './useMyStore';
// Mock the entire module containing the custom hook
jest.mock('./useMyStore');
describe('MyComponent with Custom Hook Mock', () => {
it('should render mock store data', () => {
// Cast the imported hook as a Jest mock and define its return value
useMyStore.mockReturnValue({
items: ['Item 1', 'Item 2'],
loading: false,
});
render(<MyComponent />);
expect(screen.getByText('Item 1')).toBeInTheDocument();
expect(screen.getByText('Item 2')).toBeInTheDocument();
});
});By using either of these two methods, you can cleanly mock external state syncing and focus your tests on how your components render and interact with that state.