Yanick Rochon
Yanick Rochon

Reputation: 53531

How to test server side render with @testing-library/react?

I have two tests :

import { render, screen, act } from '@testing-library/react'

describe('Test', () => {

  it('should CSR', async () => {
    await act(async () => {
      render(<div data-testid="output">Test</div>);
    });
    
    expect(screen.getByTestId('output')).toHaveTextContent('Test');
  });

  it('should SSR', async () => {
    await act(async () => {
      render(<div data-testid="output">Test</div>, { hydrate: true });
    });

    expect(screen.getByTestId('output')).toHaveTextContent('Test');
  });

});

The first test, CSR, is fine, no problem whatsoever!

The second test, SSR, spouts a bunch of errors :

  console.error
    Warning: Expected server HTML to contain a matching <div> in <div>.
        at div
  console.error
    Error: Uncaught [Error: Hydration failed because the initial UI does not match what was rendered on the server.]
        at reportException (/path/to/project/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24)
        ... a LOT more stacktrace
  console.error
    Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.

... etc.

Basically, I need to test a component through SSR. How do I do that with the test above?

My jest.config.json looks like this:

{
  "bail": 1,
  "verbose": true,
  "preset": "ts-jest",
  "testEnvironment": "jsdom",
  "setupFilesAfterEnv": ["@testing-library/jest-dom", "@testing-library/jest-dom/extend-expect"],
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
  "transformIgnorePatterns": ["<rootDir>/node_modules/"]
}

Upvotes: 1

Views: 3644

Answers (1)

Lin Du
Lin Du

Reputation: 102207

When setting the hydrate: true, RTL will call ReactDOM.hydrate() method to render the component. See source code v11.2.7/src/pure.js#L59:

act(() => {
  if (hydrate) {
    ReactDOM.hydrate(wrapUiIfNeeded(ui), container);
  } else {
    ReactDOM.render(wrapUiIfNeeded(ui), container);
  }
});

The warning Expected server HTML to contain a matching XXX was thrown by the ReactDOM.hydrate() method, not RTL.

The hydrate(reactNode, domNode) accepts two parameters:

reactNode: The “React node” used to render the existing HTML. This will usually be a piece of JSX like which was rendered with a ReactDOM Server method such as renderToString() in React 17.

domNode: A DOM element that was rendered as the root element on the server.

The div is the React Node and the container.innerHTML is the DOM node in your case.

We often use the below code to get the root DOM node and hydrate. The HTML was rendered on the server and sent to the client. The client will download the SPA bundle js file which contains below code:

const app = document.getElementById( "app" );
ReactDOM.hydrate( <App />, app )

The test should be:

import { render, screen, act } from '@testing-library/react';
import ReactDOMServer from 'react-dom/server';
import '@testing-library/jest-dom';
import React from 'react';

describe('Test', () => {
  it('should CSR', () => {
    render(<div data-testid="output">Test</div>);

    expect(screen.getByTestId('output')).toHaveTextContent('Test');
  });

  it('should SSR', () => {
    const ui = <div data-testid="output">Test</div>;
    const container = document.createElement('div');
    document.body.appendChild(container);
    container.innerHTML = ReactDOMServer.renderToString(ui);
    // hydrate the React Component and DOM node rendered on the server-side.
    render(ui, { hydrate: true, container });

    expect(screen.getByTestId('output')).toHaveTextContent('Test');
  });
});

Test result:

 PASS  stackoverflow/75376392/index.test.tsx (9.475 s)
  Test
    ✓ should CSR (19 ms)
    ✓ should SSR (7 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        10.288 s

jest.config.js:

module.exports = {
  preset: 'ts-jest/presets/js-with-ts',
  testEnvironment: 'jsdom'
}

package versions:

"react": "^16.14.0",
"react-dom": "^16.14.0",
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "^11.2.7",

P.S. Check this React test case renders over an existing text child without throwing

See hydrate#caveats

hydrate expects the rendered content to be identical with the server-rendered content. React can patch up differences in text content, but you should treat mismatches as bugs and fix them.

Upvotes: 1

Related Questions