admin管理员组

文章数量:1400096

Given a simple App containing multiple lazy loaded routes,

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="/ponent"
        exact
        render={() => (
          <Suspense fallback={<div>Loading..</div>}>
            <Component />
          </Suspense>
        )}
      />

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

How can tests be made to check if those ponents are being rendered on that specific route?

Here's the App.test with what I tried:

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

All 3 assertions don't seem to be working due to Suspense; A working snippet can be found at codesandbox here - Make sure to go on the 'tests' tab in order to see the failing tests.

Any suggestion is highly appreciated, thank you in advance!

Given a simple App containing multiple lazy loaded routes,

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="/ponent"
        exact
        render={() => (
          <Suspense fallback={<div>Loading..</div>}>
            <Component />
          </Suspense>
        )}
      />

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

How can tests be made to check if those ponents are being rendered on that specific route?

Here's the App.test with what I tried:

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

All 3 assertions don't seem to be working due to Suspense; A working snippet can be found at codesandbox here - Make sure to go on the 'tests' tab in order to see the failing tests.

Any suggestion is highly appreciated, thank you in advance!

Share Improve this question asked Jan 16, 2021 at 10:20 ale917kale917k 1,7887 gold badges22 silver badges42 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 7 +100

This is an interesting question which is hard to have a best way to mock since the lazy(() => import('path/to/file')) takes a function as argument so we can't detect the value of anonymous function.

But I think I have a solution for you but it's not best to test all cases but a specific case it would work. You would mock as following:


jest.mock('react', () => {
  const React = jest.requireActual('react');
 
  // Always render children as our lazy mock ponent
  const Suspense = ({ children }) => {
    return children;
  };

  const lazy = () => {
    // `require` ponent directly as we want to see
    // Why? Above reason
    return require('./PageNotFound').default;
  }

  return {
    ...React,
    lazy,
    Suspense
  };
});

Update a new way to mock lazy function

I think I have a better idea to invoke the lazy argument then return as a ponent as following:

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

Then you have to wait the ponent updated which is returned in mock lazy so we wait ponent to re-paint as following:

// 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={["/ponent"]}>
      <App />
    </MemoryRouter>
  );

  await waitForComponentToPaint(wrapper);

  expect(wrapper.exists(Component)).toEqual(true);
});

Another update for link

I've created a repl.it link for you to check how it works: https://repl.it/@tmhao2005/js-cra

You can run the test: yarn test -- lazy. And browse the code under src/Lazy.

Below is my working version :

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 ponent
  const Suspense = ({
    children,
  }) => children;

  const lazyImport = jest.fn().mockImplementation(() => {
    class SpyComponent extends ReactActual.Component {
      ponentDidMount() {}

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

And I added a path props when calling the lazy ponent :

   <CustomLazyComponent
      path="./CustomLazyComponent"
    />

本文标签: javascriptTest lazy loaded components in EnzymeStack Overflow