How to Mock SSR in React

Testing React applications that support Server-Side Rendering (SSR) often requires simulating the server environment within your client-side testing suite. This article explains how to mock SSR in React using Jest and React Testing Library, detailing how to simulate the absence of browser globals, test the hydration process, and mock environment-specific flags to ensure your components function seamlessly on both the server and the client.

Why Mock SSR in React?

During SSR, React components are rendered to HTML on a Node.js server where browser APIs like window, document, and localStorage do not exist. If your components access these APIs directly during the initial render, they will throw errors on the server. Mocking SSR allows you to: * Catch “window is not defined” errors before deployment. * Test conditional rendering logic that depends on the execution environment. * Ensure hydration matches the server-generated HTML to avoid layout shifts.


Method 1: Mocking the Window Object in Jest

The most direct way to simulate an SSR environment in a Jest (JSDOM) environment is to temporarily delete or modify the global window object before executing your tests.

How to delete window for specific tests:

describe('SSR Component Behavior', () => {
  const originalWindow = global.window;

  beforeEach(() => {
    // Delete the window object to simulate Node.js server environment
    // @ts-ignore
    delete global.window;
  });

  afterEach(() => {
    // Restore the window object after the test
    global.window = originalWindow;
  });

  it('should render safely without throwing window errors', () => {
    // Render your component here
    // Verify it handles the undefined window gracefully
  });
});

Method 2: Simulating Hydration in React Testing Library

Hydration is the process where React attaches event listeners to the static HTML sent by the server. You can mock this process in your tests to ensure your client-side code correctly “picks up” the server-rendered markup.

You can use react-dom/server to generate the initial HTML, inject it into the JSDOM container, and then hydrate it.

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { hydrateRoot } from 'react-dom/client';
import { screen } from '@testing-library/react';

function MySSRComponent() {
  const [isClient, setIsClient] = React.useState(false);
  
  React.useEffect(() => {
    setIsClient(true);
  }, []);

  return (
    <div>
      {isClient ? 'Client Rendered' : 'Server Rendered'}
    </div>
  );
}

test('should hydrate server-rendered HTML correctly', () => {
  const container = document.createElement('div');
  document.body.appendChild(container);

  // 1. Generate server-side HTML
  const serverHtml = ReactDOMServer.renderToString(<MySSRComponent />);
  container.innerHTML = serverHtml;

  // Assert it shows server content initially
  expect(container.textContent).toBe('Server Rendered');

  // 2. Hydrate on the "client"
  hydrateRoot(container, <MySSRComponent />);

  // Assert it updates to client content after hydration (useEffect runs)
  expect(screen.getByText('Client Rendered')).toBeInTheDocument();

  // Cleanup
  document.body.removeChild(container);
});

Method 3: Mocking Environment Flags

Many React frameworks (like Next.js or Remix) or custom setups use utility functions or environment variables to determine if the code is running on the server or client (e.g., const isServer = typeof window === 'undefined').

You can mock these helper modules in Jest to force your components to run their “server” code path.

Example utility (env.js):

export const isServer = () => typeof window === 'undefined';

Mocking the utility in Jest:

import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
import * as env from './env';

jest.mock('./env', () => ({
  isServer: jest.fn(),
}));

test('renders server-specific UI', () => {
  // Mock isServer to return true
  env.isServer.mockReturnValue(true);

  render(<MyComponent />);
  expect(screen.getByText('Loading on server...')).toBeInTheDocument();
});