【问题标题】:Mock out imported Lazy React component模拟导入的 Lazy React 组件
【发布时间】:2020-01-13 08:38:03
【问题描述】:

这是我的惰性组件:

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>

但我得到的是&lt;lazy /&gt; 而不是&lt;Bones /&gt;,但它没有通过测试。

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

【问题讨论】:

    标签: javascript reactjs sinon javascript-import react-suspense


    【解决方案1】:

    您不需要使用.then(x =&gt; x.default) 来解析lazy() 函数,React 已经为您做到了。

    React.lazy 接受一个必须调用动态 import() 的函数。这必须返回一个 Promise,它解析为具有包含 React 组件的默认导出的模块。 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()
    })
    
    

    实时示例:点击浏览器标签旁边的测试标签。如果它不起作用,只需重新加载页面即可。

    您可以在他们的Docs 网站上找到更多react-testing-library 复杂示例。

    【讨论】:

    • 这不能用sinon吗?我宁愿不必说服我的团队更改 1000 多个测试,这样我就可以涵盖一件事。
    • 当然可以,这些例子只是为了理解,你可以按照同样的原则做更复杂的。也许this 会有所帮助。
    • 我看了一下,在 sinon 中找不到等价物。因此“使用不同的测试库”没有用。
    • @Pureferret 您可以直接使用 jest.spyOn 来监视对象的行为,例如 jest.spyOn(props, 'onChange')。否则,请记住 jest.fn() 返回具有 sinon 等属性的对象。
    • @xargr 我没有在开玩笑(很遗憾)。这个答案不涉及开玩笑。我不知道你为什么要提起它?
    【解决方案2】:

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

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

    【讨论】:

    • 这对我挂载的组件的结果没有影响
    【解决方案3】:

    我不确定这是否是您正在寻找的答案,但听起来问题的一部分是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 一起使用。

    【讨论】:

    • 不幸的是,真正的骨骼组件必须被存根,因为它包含一个 es6 导入。但这对其他人来说是一个很好的答案。
    【解决方案4】:

    我需要使用 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,但适用于 Enzyme 库。

    【讨论】:

    • 诗浓是怎么做到的?
    猜你喜欢
    • 2019-04-10
    • 1970-01-01
    • 2018-11-26
    • 2018-04-03
    • 2017-01-14
    • 1970-01-01
    • 2020-12-03
    • 2019-06-21
    • 1970-01-01
    相关资源
    最近更新 更多