模拟导入的 Lazy React 组件

Mock out imported Lazy React component

这是我的惰性组件:

const LazyBones = React.lazy(() => import('@graveyard/Bones')
  .then(module => ({default: module.BonesComponent}))
export default LazyBones

我是这样导入的:

import Bones from './LazyBones'

export default () => (
<Suspense fallback={<p>Loading bones</p>}>
  <Bones />
</Suspense>
)

在我的测试中我遇到了这样的事情:

import * as LazyBones from './LazyBones';

describe('<BoneYard />', function() {
  let Bones;
  let wrapper;
  beforeEach(function() {
    Bones = sinon.stub(LazyBones, 'default');
    Bones.returns(() => (<div />));
    wrapper = shallow(<BoneYard />);
  });
  afterEach(function() {
    Bones.restore();
  });

  it('renders bones', function() {
    console.log(wrapper)
    expect(wrapper.exists(Bones)).to.equal(true);
  })

})

我期望的是测试通过,并打印出 console.log:

<Suspense fallback={{...}}>
  <Bones />
</Suspense>

但我得到的不是 <Bones />,而是 <lazy />,它未通过测试。

如何模拟导入的 Lazy React 组件,以便我的简单测试通过?

您不需要使用 .then(x => x.default) 解析 lazy() 函数,React 已经为您完成了。

React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component. React code splitting

语法应该类似于:

const LazyBones = React.lazy(() => import("./LazyBones"))

示例:

// LazyComponent.js
import React from 'react'

export default () => (
  <div>
    <h1>I'm Lazy</h1>
    <p>This component is Lazy</p>
  </div>
)

// App.js
import React, { lazy, Suspense } from 'react'
// This will import && resolve LazyComponent.js that located in same path
const LazyComponent = lazy(() => import('./LazyComponent'))

// The lazy component should be rendered inside a Suspense component
function App() {
  return (
    <div className="App">
      <Suspense fallback={<p>Loading...</p>}>
        <LazyComponent />
      </Suspense>
    </div>
  )
}


至于测试,您可以按照create-react-app中默认提供的React测试示例进行一些更改。

创建一个名为 LazyComponent.test.js 的新文件并添加:

// LazyComponent.test.js
import React, { lazy, Suspense } from 'react'
import { render, screen } from '@testing-library/react'

const LazyComponent = lazy(() => import('./LazyComponent'))

test('renders lazy component', async () => {
  // Will render the lazy component
  render(
    <Suspense fallback={<p>Loading...</p>}>
      <LazyComponent />
    </Suspense>
  )
  // Match text inside it
  const textToMatch = await screen.findByText(/I'm Lazy/i)
  expect(textToMatch).toBeInTheDocument()
})

实时示例: 单击 测试 选项卡就在 浏览器 选项卡旁边。如果它不起作用,请重新加载页面。

您可以找到更多 react-testing-library complex examples at their Docs 网站。

要嘲笑你的惰性组件首先想到的是将测试转换为异步测试并等待组件存在,例如:

import CustomComponent, { Bones } from './Components';

it('renders bones', async () => {
   const wrapper = mount(<Suspense fallback={<p>Loading...</p>}>
                       <CustomComponent />
                   </Suspense>

   await Bones;
   expect(wrapper.exists(Bones)).toBeTruthy();
}

我不确定这是您要找的答案,但听起来问题的一部分是 shallow。根据 this threadshallow 不适用于 React.lazy

然而,mount 在尝试存根惰性组件时也不起作用 - 如果您调试 DOM 输出(使用 console.log(wrapper.debug())),您可以看到 Bones 在 DOM 中,但它是真实的(未删除的)版本。

好消息:如果您只是想检查 Bones 是否存在,则根本不必模拟该组件!此测试通过:

import { Bones } from "./Bones";
import BoneYard from "./app";

describe("<BoneYard />", function() {
  it("renders bones", function() {
    const wrapper = mount(<BoneYard />);
    console.log(wrapper.debug());
    expect(wrapper.exists(Bones)).to.equal(true);
    wrapper.unmount();
  });
});

如果您出于其他原因确实需要模拟组件,jest 会让您这样做,但听起来您似乎在试图避免 jestThis threadjest 的上下文中讨论了一些其他选项(例如 mocking Suspense and lazy) 也可以与 sinon.

一起使用

我需要使用 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,但对于酶库来说效果很好。