在 Enzyme 中测试延迟加载的组件

Test lazy loaded components in Enzyme

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

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 个断言似乎都不起作用;可在 codesandbox here 找到工作片段 - 确保继续 'tests' 选项卡以查看失败的测试。

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

这是一个有趣的问题,很难找到最好的模拟方法,因为 lazy(() => 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 参数然后 return 作为组件如下:

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

然后你必须等待组件更新returned in mock 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);
});

link

的另一个更新

我创建了一个 repl.it link 供您检查其工作原理:https://repl.it/@tmhao2005/js-cra

您可以运行 测试:yarn test -- lazy。并浏览 src/Lazy.

下的代码

以下是我的工作版本:

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 props :

   <CustomLazyComponent
      path="./CustomLazyComponent"
    />