如何使用Jest和新的React lazy 16.6 API测试快照

11

我已经使用新的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>
  );
 }
}

在我的测试中,我正在对此组件进行快照。这是一个非常简单的测试:

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

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

expect(tree).toMatchSnapshot();

在日志中,测试失败并出现以下错误:

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.

每个测试套件都需要用 <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(); 

};

如果我这样做,我只会在快照中看到fallback组件。
+ Array [ + <div> + loading + </div>, + ]

那么,哪种方法是最好的呢?


你尝试在await行之后调用toJSON了吗?我猜你是在未解决的组件上调用了toJSON,而await得到了这个结果,而不是等待CustomComponent被解决。 - Andreas Köberle
你好 @AndreasKöberle,我刚试了一下,它不起作用。在快照中返回回退组件:const component = await create( loading
}> ); const tree = component.toJSON(); expect(tree).toMatchSnapshot();还有快照:+ Array [ +
+ loading +
, + ]
- Albert Olivé Corbella
如果我使用setTimeout,它可以工作,但这并不理想... - Albert Olivé Corbella
5个回答

13

我每个测试套件都需要用 <Suspense> 包装吗?

是的,Suspense 组件对于懒加载子组件是必要的,特别是提供回退和在懒加载组件可用时进行协调。

CustomComponent 中导出 Component1Component2,以便它们可以在测试中被导入。

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

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

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

记住,延迟加载的组件类似于Promise。在测试中导入它们,并等待它们解析后再进行快照匹配检查。

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();
  })
})

你知道如何处理嵌套组件的情况吗?我们是否需要显式地导入所有组件以便能够使用 await - skyboyer
1
我不确定测试嵌套组件的方式是否有用例; 我猜显式导入它们就可以了。通常大多数组件只关心组件树下一级,因此如果您测试上一级,那么应该足够测试该组件。 - Oluwafemi Sule
没错,当然, shallow 在这里拯救了一天。 - skyboyer
5
lazy(() => import('./Component1')) 返回一个 React 对象组件,因此使用 await Component1 并不能起作用,它会立即解析并不会给懒加载组件足够的时间来加载。Suspense 仍在渲染回退属性。相反,我能够使用 await import('./Component1')await import('./Component2')。我假设测试套件可能会在 react-test-renderer 之前解析导入组件,但我还没有遇到这种竞态条件。 - Andrew Ferk

5
根据这个Github评论,你可以使用Jest模拟懒加载组件以返回实际组件,但你需要将懒加载语句移动并导出到它们自己的文件中才能使其正常工作。
// 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(); 
  });
});

3

使用 Enzyme 和 mount,这对我起作用了。它不需要改变任何导出。

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

jest.runOnlyPendingTimers()
wrapper.update()

感谢安德鲁·费克在被接受答案的评论中提供的帮助。


你让我的一天变得美好,谢谢你! :) - Alejandro

2

我曾经遇到过类似的问题,想对嵌套组件进行快照测试,其中一个组件是懒加载的。嵌套关系如下:

SalesContainer -> SalesAreaCard -> SalesCard -> AreaMap

SalesContainer 是顶层组件,AreaMap 组件由 SalesCard 通过 React lazy 和 Suspense 进行懒加载。这些测试在大多数开发人员本地运行时都通过了,但是在 Jenkins CI 上始终无法成功,AreaMap 没有被渲染出来,可以说是不稳定。

为了使测试通过,我在测试中添加了神奇的代码行 await testRenderer.getInstance().loadingPromise;。以下是一个测试示例:

最初的回答

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();
  });
});

0

在某种程度上,两个答案的结合是对我有效的唯一方法(使用enzyme mount)

我必须使用被接受的答案的导出逻辑和Raine的答案的等待逻辑。

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

Raine的答案在我们的构建代理上不知何故无法工作,但在开发环境中可以工作


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接