How to test snapshots with Jest and new React lazy 16.6 API

16.7k Views Asked by At

I have to components imported with the new React lazy API (16.6).

import React, {PureComponent, lazy} from 'react';

const Component1 = lazy(() => import('./Component1'));
const Component2 = lazy(() => import('./Component2'));

class CustomComponent extends PureComponent {
  ...
  render() {

  return (
    <div>
      <Component1 />
      <Component2 />
    </div>
  );
 }
}

In my tests, I'm doing the snapshots of this component. It's a very straightforward test:

import { create } from 'react-test-renderer';

const tree = await create(<CustomComponent />).toJSON();

expect(tree).toMatchSnapshot();

In the logs, the test is failing with this error:

A React component suspended while rendering, but no fallback UI was specified.

Add a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.

Do I have to wrap in every single test suite with <Suspense>...?

it('should show the component', async () => {
  const component = await create(
    <React.Suspense fallback={<div>loading</div>}>
     <CustomComponent /> 
    </React.Suspense> 
  ); 
  const tree = component.toJSON(); 

  expect(tree).toMatchSnapshot(); 

};

If I do that, I only see in the snapshot the fallback component.

+ Array [ + <div> + loading + </div>, + ]

So, which is the best way to do it?

5

There are 5 best solutions below

4
On BEST ANSWER

Do I have to wrap in every single test suite with <Suspense>?

Yes, the Suspense component is neccessary for lazily loading child components, particularly providing a fallback and for reconciliation when the lazy components are available.

Export Component1 and Component2 in CustomComponent so that they can be imported in tests.

import React, {PureComponent, lazy} from 'react';

export const Component1 = lazy(() => import('./Component1'));
export const Component2 = lazy(() => import('./Component2'));

export default class CustomComponent extends PureComponent {
  //...
}

Remember that the lazy loaded components are promise-like. Import them in the test, and wait for them to resolve before doing a check that the snapshot matches.

import { create } from 'react-test-renderer';
import React, {Suspense} from 'react';
import CustomComponent, {Component1, Component2} from './LazyComponent';

describe('CustomComponent', () => {
  it('rendered lazily', async()=> {
    const root = create(
      <Suspense fallback={<div>loading...</div>}>
        <CustomComponent/>
      </Suspense>
    );

    await Component1;
    await Component2;
    expect(root).toMatchSnapshot();
  })
})
0
On

In a way a combination of the two answers is the only thing that worked for me (using enzyme mount)

I had to use the export logic of the accepted answer and the await logic of Raine's answer

//use at own risk
await (Component1 as any)._ctor();
wrapper.update()

Raine's answer did not work on our build agent somehow, but worked on the dev environment

0
On

As per this comment in github, you can mock the lazy components with Jest to return the actual components instead, although you would need to move and export lazy statements to their own files for it to work.

// LazyComponent1.ts
import { lazy } from 'react';

export default lazy(() => import('./Component1'));
// CustomComponent.tsx
import React, { PureComponent } from 'react';
import Component1 from './LazyComponent1';
import Component2 from './LazyComponent2';

class CustomComponent extends PureComponent {
  ...
  render() {

  return (
    <div>
      <Component1 />
      <Component2 />
    </div>
  );
 }
}
// CustomComponent.spec.tsx
import React, { Suspense } from 'react';
import { create } from 'react-test-renderer';
import CustomComponent from './CustomComponent';

jest.mock('./LazyComponent1', () => require('./Component1'));
jest.mock('./LazyComponent2', () => require('./Component2'));

describe('CustomComponent', () => {
  it('should show the component', () => {
    const component = await create(
      <Suspense fallback={<div>loading</div>}>
       <CustomComponent /> 
      </Suspense> 
    ); 
    const tree = component.toJSON(); 

    expect(tree).toMatchSnapshot(); 
  });
});
0
On

I had a similar problem where I wanted to do snapshot testing of nested components and one of them where lazy loaded. The nesting looked something like this:

SalesContainer -> SalesAreaCard -> SalesCard -> AreaMap

Where SalesContainer is the top component. The AreaMap-component is lazy loaded by SalesCard using React lazy and Suspense. The tests passed locally with AreaMap rendered in the snapshot for most developers. But the tests always failed miserably in Jenkins CI with the AreaMap never rendered. Flaky to say the least.

To make the tests pass I added the magic line await testRenderer.getInstance().loadingPromise; to the tests. This is an example of a test:

import React from 'react';
import renderer from 'react-test-renderer';
import wait from 'waait';
import SalesContainer from './index';

describe('<SalesContainer />', () => {
it('should render correctly', async () => {
    const testRenderer = renderer.create(
      <SalesContainer />
    );
    await wait(0);
    await testRenderer.getInstance().loadingPromise;
    expect(testRenderer).toMatchSnapshot();
  });
});
1
On

With Enzyme and mount this worked for me. It does not require changing any exports.

// wait for lazy components
await import('./Component1')
await import('./Component2')

jest.runOnlyPendingTimers()
wrapper.update()

Thanks to Andrew Ferk's comment on the accepted answer.