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

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.