How to test snapshots with Jest and new React lazy 16.6 API
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();
});
});
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.
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();
});
});
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();
})
})