【问题标题】:Test lazy loaded components in Enzyme在 Enzyme 中测试延迟加载的组件
【发布时间】:2021-04-21 05:13:07
【问题描述】:

给定一个包含多个延迟加载路由的简单应用,

import React, { lazy, Suspense } from "react";
import { Route } from "react-router-dom";
import "./styles.css";

const Component = lazy(() => import("./Component"));
const PageNotFound = lazy(() => import("./PageNotFound"));

export default function App() {
  return (
    <div className="App">
      <Route
        path="/component"
        exact
        render={() => (
          <Suspense fallback={<div>Loading..</div>}>
            <Component />
          </Suspense>
        )}
      />

      <Route
        path="*"
        render={() => (
          <Suspense fallback={<div>Loading..</div>}>
            <PageNotFound />
          </Suspense>
        )}
      />
    </div>
  );
}

如何进行测试以检查这些组件是否在该特定路径上呈现?

这是我尝试过的 App.test:

import { configure, shallow, mount } from "enzyme";
import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
import React from "react";
import { MemoryRouter } from "react-router-dom";
import App from "./App";
import Component from "./Component";
import PageNotFound from "./PageNotFound";

configure({ adapter: new Adapter() });

describe("App", () => {
  it("renders without crashing", () => {
    shallow(<App />);
  });

  it("renders lazy loaded PageNotFound route", () => {
    // Act
    const wrapper = mount(
      <MemoryRouter initialEntries={["/random"]}>
        <App />
      </MemoryRouter>
    );

    // Assert
    // expect(wrapper.containsMatchingElement(<PageNotFound />)).toEqual(true);
    // expect(wrapper.find(PageNotFound)).toHaveLength(1);
    expect(wrapper.exists(PageNotFound)).toEqual(true);
  });
});

由于 Suspense,所有 3 个断言似乎都不起作用;可以在代码和框 here 找到有效的 sn-p - 确保进入“测试”选项卡以查看失败的测试。

任何建议都非常感谢,提前谢谢!

【问题讨论】:

    标签: javascript reactjs jestjs enzyme


    【解决方案1】:

    这是一个有趣的问题,很难找到最好的模拟方法,因为lazy(() =&gt; import('path/to/file')) 将函数作为参数,因此我们无法检测匿名函数的值。

    但我想我有一个适合你的解决方案,但最好不要测试所有案例,而是测试一个特定的案例。你会模拟如下:

    
    jest.mock('react', () => {
      const React = jest.requireActual('react');
     
      // Always render children as our lazy mock component
      const Suspense = ({ children }) => {
        return children;
      };
    
      const lazy = () => {
        // `require` component directly as we want to see
        // Why? Above reason
        return require('./PageNotFound').default;
      }
    
      return {
        ...React,
        lazy,
        Suspense
      };
    });
    

    更新模拟lazy函数的新方法

    我认为我有一个更好的主意来调用lazy 参数,然后作为组件返回,如下所示:

    jest.mock('react', () => {
      const React = jest.requireActual('react');
      const Suspense = ({ children }) => {
        return children;
      };
      
      const lazy = jest.fn().mockImplementation((fn) => {
        const Component = (props) => {
          const [C, setC] = React.useState();
    
          React.useEffect(() => {
            fn().then(v => {
              setC(v)
            });
          }, []);
    
          return C ? <C.default {...props} /> : null;
        }
    
        return Component;
      })
    
      return {
        ...React,
        lazy,
        Suspense
      };
    });
    

    然后您必须等待在模拟lazy 中返回的组件更新,因此我们等待组件重新绘制如下:

    // keep warning `act` removed
    import { act } from 'react-dom/test-utils';
    
    // A helper to update wrapper
    const waitForComponentToPaint = async (wrapper) => {
      await act(async () => {
        await new Promise(resolve => setTimeout(resolve));
        wrapper.update();
      });
    };
    
    it("renders PageNotFound", async () => {    
      const wrapper = mount(
        <MemoryRouter initialEntries={["/random"]}>
          <App />
        </MemoryRouter>
      );
    
      await waitForComponentToPaint(wrapper);
    
      expect(wrapper.exists(PageNotFound)).toEqual(true);
    });
    
    it("renders Component", async () => {    
      const wrapper = mount(
        <MemoryRouter initialEntries={["/component"]}>
          <App />
        </MemoryRouter>
      );
    
      await waitForComponentToPaint(wrapper);
    
      expect(wrapper.exists(Component)).toEqual(true);
    });
    
    

    链接的另一个更新

    我创建了一个repl.it 链接供您查看它是如何工作的:https://repl.it/@tmhao2005/js-cra

    您可以运行测试:yarn test -- lazy。并浏览src/Lazy下的代码。

    【讨论】:

    • 如果我们只有一条路线,这确实很有效,但如果我们有很多路线,我们最终会用&lt;PageNotFound /&gt; 代替所有路线
    • 我为你放弃了另一个建议
    • 它现在将所有&lt;lazy /&gt; 更新为&lt;Component /&gt;,所以它仍然无法通过这些检查.. 真的很感谢你的努力!如果你想直接测试新想法,我为它做了一个游乐场:codesandbox.io/s/musing-oskar-hjezp
    • 不确定您遇到了什么问题,但我为您创建了一个repl.it,然后您可以跟进您错过的问题。
    • 对不起,你是对的,这行得通!我错过了异步调用waitForComponentToPaint 的一点点——现在一切正常,除了PageNotFound 组件,因为它似乎存在于任何路线上?如果您尝试在“它呈现组件”下添加此断言expect(wrapper.exists(PageNotFound)).toEqual(true);,它仍然会成功通过,但这是一种不同类型的问题 - 您还记得任何检查组件是否应该显示的断言吗?
    【解决方案2】:

    以下是我的工作版本:

    import { act, } from 'react-dom/test-utils';
    
    const waitForComponentToPaint = async (wrapper) => {
      await act(async () => {
        await new Promise((resolve) => setTimeout(resolve));
        wrapper.update();
      });
    };
    
    jest.mock('react', () => {
      const ReactActual = jest.requireActual('react');
    
      // Always render children as our lazy mock component
      const Suspense = ({
        children,
      }) => children;
    
      const lazyImport = jest.fn().mockImplementation(() => {
        class SpyComponent extends ReactActual.Component {
          componentDidMount() {}
    
          render() {
            const {
              path,
            } = this.props;
            const LazyComponent = require(path).default;
            return (
              <>
                {LazyComponent ? <LazyComponent {...this.props} /> : null}
              </>
            );
          }
        }
    
        return SpyComponent;
      });
    
      return {
        ...ReactActual,
        lazy: lazyImport,
        Suspense,
      };
    });
    
    describe('Render <Header />', () => {
        it('should render a Header', async () => {
          const wrapper = mount(
              <Header />
          );
          await waitForComponentToPaint(wrapper);
          expect(wrapper.find('XXXXXX')).to.have.length(1);
        });          
     });
    

    并且我在调用惰性组件时添加了一个path 道具:

       <CustomLazyComponent
          path="./CustomLazyComponent"
        />
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-23
      • 1970-01-01
      • 1970-01-01
      • 2020-10-17
      • 2022-11-11
      • 1970-01-01
      • 2019-02-01
      • 1970-01-01
      相关资源
      最近更新 更多