silviubogan
silviubogan

Reputation: 3461

How to mock loadable.lib to make the library be loaded synchronously in some unit tests?

We now use lazy-loading through loadable.lib for about 20 new files which used to load the npm module react-toastify synchronously. The changes are waiting in a draft PR but it seems that the unit tests are broken because they do not wait for the loadable.lib-passed module to be loaded.

Expected results

Be able to mock loadable.lib so that it works exactly like before but loads the given library synchronously and in the unit test this is seen as the children of loadable.lib resulted Component have access to that library and a first render does this successfully.

Actual results

The old snapshot (full of tags and nested things and props) and the new one (null) are not matching. These do not work:

// TODO: not working because loadable is used in many places
// and children are not always enough to render to avoid crashes,
// and even just with children there can be crashes
jest.mock('@loadable/component', (loadfn) => ({
  lib: jest.fn(() => {
    return { toast: {} };
  }),
}));

If it is possible to mock the loadable.lib function to render its children instead of wait for some library to be loaded, I don't know how I can fill the undefined variables that the code uses because I have loadables that use loadables that use loadables and so on.

I've read that there are some WebPack hints such as webpackPrefetch and webpackPreload but I am not sure if it is a good road to go.

Relevant links on what I have tried

  1. The code I am working on (and there are 19 other files like this one): https://github.com/silviubogan/volto/blob/1d015c145e562565ecfa058629ae3d7a9f3e39e4/src/components/manage/Actions/Actions.jsx (I am currently working on loading react-toastify through loadable.lib always.)

  2. https://medium.com/pixel-and-ink/testing-loadable-components-with-jest-97bfeaa6da0b - I tried to do a similar thing like the code in that article but it is not working:

jest.mock('@loadable/component', async (loadfn) => {
   const val = await loadfn();
   return {
     lib: () => val,
   };
});

A little bit of code

Taken from the link above, this is how I currently use react-toastify (named LoadableToast):

/**
 * Render method.
 * @method render
 * @returns {string} Markup for the component.
 */
render() {
  return (
    <LoadableToast>
      {({ default: toast }) => {
        this.toast = toast;

        return (
          <Dropdown
            item
            id="toolbar-actions"

Conclusion

To put it in other words, how can I mock a dynamic import? How can I make jest go over lazy loading and provide a value instead of making the test wait to receive a value?

Thank you!

Update 1

With the following new code, still not working:

jest.mock('@loadable/component', (load) => {
  return {
    lib: () => {
      let Component;
      const loadPromise = load().then((val) => (Component = val.default));
      const Loadable = (props) => {
        if (!Component) {
          throw new Error(
            'Bundle split module not loaded yet, ensure you beforeAll(() => MyLazyComponent.load()) in your test, import statement: ' +
              load.toString(),
          );
        }
        return <Component {...props} />;
      };
      Loadable.load = () => loadPromise;
      return Loadable;
    },
  };
});

Upvotes: 1

Views: 2470

Answers (2)

Guillermo Pincay
Guillermo Pincay

Reputation: 116

A better solution is to mock the Loadable components module. We can do this simply by using Jest mock capabilities (https://jestjs.io/docs/manual-mocks#mocking-node-modules) and Loadable itself in the following way:

  1. Create a @loadable directory inside the mocks folder at the root level of your project (mocks should be at the same level of node_modules).
  2. Inside the @loadable folder, create a component.js file. In this file we will mock the loadable function by writing the code below:
export default function (load) {
  return load.requireSync();
}

That's it, now you should be able to run your unit tests as normal.

Upvotes: 4

Tobias Andersson
Tobias Andersson

Reputation: 21

We did it by

jest.mock('@loadable/component', () => ({
    lib: () => {
        const MegadraftEditor = () => {} // Name of your import
        return ({ children }) => children({ default: { MegadraftEditor } })
    },
}))

In case there is only one default export it could also be

jest.mock('@loadable/component', () => ({
lib: () => {
    const HotTable = () => {} // Name of your import
    return ({ children }) => children({ default: HotTable })
},
}))

Then in the test you just need to dive() and use childAt() until you have the correct location of the component you want to lazy-load. Note that this will then not have any of your wrappers around the lazy-loaded component in the snapshot.

it('renders correct', () => {
    const component = shallow(
        <View
            data={}
        />
    )
        .dive()
        .childAt(0)
        .dive()

    expect(component).toMatchSnapshot()
})

Upvotes: 2

Related Questions