How to Mock Client-Side Rendering in React
Testing React components that rely on client-side rendering (CSR)
often requires simulating browser-specific environments and APIs in
Node.js-based test runners like Jest or Vitest. This article explains
how to mock CSR behavior, simulate browser globals such as
window and document, and mock client-only
hooks to ensure your components test successfully without a real
browser.
Mocking Browser Globals
When running tests in a Node environment, browser-specific APIs like
window.matchMedia, localStorage, or
sessionStorage are either missing or incomplete. To test
CSR components that rely on these APIs, you must mock them globally.
You can define these mocks directly in your test file or in a global
setup file (e.g., setupTests.js):
// Mocking window.matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
// Mocking localStorage
const localStorageMock = (() => {
let store = {};
return {
getItem: (key) => store[key] || null,
setItem: (key, value) => { store[key] = value.toString(); },
clear: () => { store = {}; },
removeItem: (key) => { delete store[key]; }
};
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });Mocking Client-Only Execution
Some React applications use conditional checks like
typeof window !== 'undefined' or custom hooks to defer
rendering until the component has mounted on the client.
If you want to force your tests to simulate either a client-side environment or a server-side environment, you can mock the window object dynamic state.
To mock a custom hook that detects CSR, such as
useIsMounted:
// useIsMounted.js
import { useState, useEffect } from 'react';
export function useIsMounted() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
return mounted;
}In your test, you can mock this hook to instantly return
true to simulate a post-mount client state:
import { render, screen } from '@testing-library/react';
import { useIsMounted } from './useIsMounted';
import MyComponent from './MyComponent';
// Mock the hook hook module
jest.mock('./useIsMounted', () => ({
useIsMounted: jest.fn(),
}));
test('renders client-only content', () => {
// Force the hook to return true (simulating CSR mounted state)
useIsMounted.mockReturnValue(true);
render(<MyComponent />);
expect(screen.getByText(/client-only features/i)).toBeInTheDocument();
});Mocking Client-Side Data Fetching
Client-side rendering frequently relies on fetching data immediately
after mounting via useEffect or libraries like TanStack
Query. To test these components without making actual network requests,
you must mock the global fetch API.
describe('Client-Side Fetching Component', () => {
beforeEach(() => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve({ data: 'Mocked Client Data' }),
})
);
});
afterEach(() => {
jest.clearAllMocks();
});
test('fetches and displays data on client mount', async () => {
render(<DataComponent />);
const dynamicContent = await screen.findByText('Mocked Client Data');
expect(dynamicContent).toBeInTheDocument();
});
});