模拟 React.Suspense,同时保持 React 的其余部分完好无损

IT技术 javascript reactjs unit-testing jestjs enzyme
2021-04-30 08:46:21

我正在尝试为使用React.Suspense的组件编写Jest单元测试

被测组件module的简化版本:

我的组件.js

import React from 'react';

export default () => <h1>Tadaa!!!</h1>;

MySuspendedComponent.js

import React, { Suspense } from 'react';
import MyComponent from './MyComponent';

export default () => (
    <Suspense fallback={<div>Loading…</div>}>
        <MyComponent />
    </Suspense>
);

天真地,在我的第一次尝试中,我编写了一个使用Enzyme挂载挂起组件的单元测试

MySuspendedComponent.test.js

import React from 'react';
import { mount } from 'enzyme';
import MySuspendedComponent from './MySuspendedComponent';

test('the suspended component renders correctly', () => {
    const wrapper = mount(<MySuspendedComponent />);
    expect(wrapper.html()).toMatchSnapshot();
});

这会导致测试崩溃并显示错误消息:

错误:酶内部错误:标签为 13 的未知节点

在网上搜索错误信息,我发现这很可能是由于 Enzyme 尚未准备好渲染Suspense(尚未准备好)引起的。

如果我使用shallow而不是mount,则错误消息将更改为:

不变性违规:ReactDOMServer 尚不支持 Suspense

我的下一次尝试是Suspense用一个虚拟的 pass-through 组件来模拟,如下所示:

MySuspendedComponent.test.js

import React from 'react';
import { mount } from 'enzyme';
import MySuspendedComponent from './MySuspendedComponent';

jest.mock('react', () => {
    const react = require.requireActual('react');
    return () => ({
        ...react,
        Suspense({ children }) {
            return children;
        }
    });
});

test('the suspended component renders correctly', () => {
    const wrapper = mount(<MySuspendedComponent />);
    expect(wrapper.html()).toMatchSnapshot();
});

这个想法是有一个 React module的模拟实现,它包含来自 React 库的所有实际代码,只Suspense被一个模拟函数替换。

我已经在其他单元测试中成功使用了这种模式requireActual,如Jest 文档中所述,在模拟除 React 之外的其他module时,成功地用于其他单元测试,但是对于 React,它不起作用。

我现在得到的错误是:

TypeError: (($_$w(...) , react) || ($ $w(...) , _load_react(...))).default.createElement 不是函数

...我认为这是由于在我的嘲讽技巧之后 React 的原始实现不可用造成的。

如何在保持 React 库的其余部分完好无损的同时模拟 Suspense?

或者还有另一种更好的方法来测试悬挂的组件吗?

2个回答

解决方法不是使用对象传播来导出原始的 React module,而是简单地覆盖Suspense属性,如下所示:

MySuspendedComponent.test.js

import React from 'react';
import { mount } from 'enzyme';
import MySuspendedComponent from './MySuspendedComponent';

jest.mock('react', () => {
    const React = jest.requireActual('react');
    React.Suspense = ({ children }) => children;
    return React;
});

test('the suspended component renders correctly', () => {
    const wrapper = mount(<MySuspendedComponent />);
    expect(wrapper.html()).toMatchSnapshot();
});

这会按预期创建以下快照:

MySuspendedComponent.test.js.snap

exports[`the suspended component renders correctly 1`] = `"<h1>Tadaa!!!</h1>"`;

我需要使用 Enzyme 测试我的惰性组件。以下方法对我有用,可以测试组件加载完成情况:

const myComponent = React.lazy(() => 
      import('@material-ui/icons')
      .then(module => ({ 
         default: module.KeyboardArrowRight 
      })
   )
);

测试代码 ->

    //mock actual component inside suspense
    jest.mock("@material-ui/icons", () => { 
        return {
            KeyboardArrowRight: () => "KeyboardArrowRight",
    }
    });
    
    const lazyComponent = mount(<Suspense fallback={<div>Loading...</div>}>
               {<myComponent>}
           </Suspense>);
        
    const componentToTestLoaded  = await componentToTest.type._result; // to get actual component in suspense
        
    expect(componentToTestLoaded.text())`.toEqual("KeyboardArrowRight");

这是 hacky 但适用于酶库。