gregberge / loadable-components

The recommended Code Splitting library for React ✂️✨
https://loadable-components.com
MIT License
7.66k stars 380 forks source link

TypeError: (0 , _component.default) is not a function mock tests #854

Open tejpowar opened 2 years ago

tejpowar commented 2 years ago

💬 Questions and Help

Hi,

We are using the loadable/component function to load components but are having issues with the mocking tests and running into the following error:

TypeError: (0 , _component.default) is not a function

Within our test we have just created a mock like so

jest.mock('@loadable/component', () => ({
  loadable: jest.fn()
}));

Also tried

jest.mock('@loadable/component', () => {
  const original = jest.requireActual('@loadable/component');
  return {
    ...original,
    __esModule: true,
    default: () => {},
    loadableReady: callback => callback(),
  };
});

But this still errors.

Any ideas on how we can mock it?

open-collective-bot[bot] commented 2 years ago

Hey @tejpowar :wave:, Thank you for opening an issue. We'll get back to you as soon as we can. Please, consider supporting us on Open Collective. We give a special attention to issues opened by backers. If you use Loadable at work, you can also ask your company to sponsor us :heart:.

theKashey commented 2 years ago
tejpowar commented 2 years ago

Hi,

We basically need to update unit tests whereby we are using loadable and need to mock it.

The error is:

Test suite failed to run

TypeError: (0 , _component.default) is not a function

  18 |
> 19 | export const agreement = loadable(
     |                               ^
  20 |   () => import(/* webpackChunkName: "agreement" */ './agreement'),
  21 |   { fallback: <Spinner overlay="clear" alignCenter /> }
  22 | );

So the error is actually failing in our component when we run our unit tests.

Basically we need to mock out the above code within our unit test

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

tara-singh-danu commented 2 years ago

Did anyone found any solution?

nathanmillar16 commented 2 years ago

I'm also looking for a solution. Any help would be appreciated!

@tara-singh-danu did you find a solution?

tara-singh-danu commented 2 years ago

I'm also looking for a solution. Any help would be appreciated!

@tara-singh-danu did you find a solution?

I am using these libraries now

"@testing-library/jest-dom": "5.16.2", "@testing-library/react": "12.1.5", "@testing-library/user-event": "^14.2.1",

In which I am not getting the above issue.
nathanmillar16 commented 2 years ago

@tejpowar how did you mock the loadable import?

HebleV commented 1 year ago

I faced a similar issue and included the function definition in a try catch block and it fixed the related failing test cases.

santicalvo commented 1 year ago

Hi @HebleV can you expand a little in the solution?

santicalvo commented 1 year ago

I have found a very hacky and ugly solution... It would be nice to know the proper way to do this.

Let's assume we import a component (with classes) called Dummy:

import loadable, { lazy } from '@loadable/component';
const Dummy = lazy(() => import('../components/Dummy'));
...
// lazyLoaded is a state variable to force Suspense
class SomeClass  {
 render() {
  return (<>
     {this.state.lazyLoaded && <Suspense fallback={<div>loading in suspense</div>}><Dummy /></Suspense>
   < />)
 }
}

We can use the following super ugly code in jest to mock it.

jest.mock('react', () => {
  const React = jest.requireActual('react');
  const Suspense = ({children, fallback}) => {
    console.log('Suspense called children!!', typeof children, children.toString());
    console.log('Suspense called fallback!!', typeof fallback, fallback.toString());
    return children;
  };
  return {
    ...React,
    Suspense
  };
});

jest.mock('@loadable/component', () => {
  const loadable = jest.requireActual('@loadable/component');
  // The trick is to convert the async dynamic import we do not have in node, to a syncronous require!!!!!!
  const lazy = (importer) => {
    const matchCondition = `${importer?.toString()}`.match('Dummy');
    if(matchCondition) {
      return require('../components/Dummy').default;
    }
    return importer;
  };
  loadable.lazy = lazy;
  return {
    __esModule: true,
    default: (children) => {
      return children;
    },
    loadable,
    lazy
  };
});
theKashey commented 1 year ago

Note - you do mock @loadable/component in the same way, and in your case default export is working 🤷‍♂️

However, I should ask other questions

santicalvo commented 1 year ago

Hello @theKashey, I mock @loadable/component because I need to mock lazy. Also, I have loadable babel plugin, injected with react-app-rewired, but not working with jest. How should it work with jest? Do you know any example to mock properly @loadable/component, Suspense and lazy?

I manage to get this working on React 17, even though it is not supported. It might work on 16, but no idea...

I would love to know the proper way to do this :-)

theKashey commented 1 year ago

How should it work with jest?

Follow Jest babel's configuration (usually just .babelrc)

Do you know any example to mock

You cannot mock loadable, as you need "something" to transform import into require to make your code "sync". Or you might not want that and keep stuff dynamic. If you use Jest with RTL - that should work out of the box, you just need to wait a little for components to load (wait for content inside lazy loaded regions or just wait for a promise)

santicalvo commented 1 year ago

You cannot mock loadable, as you need "something" to transform import into require to make your code "sync". Or you might not want that and keep stuff dynamic. If you use Jest with RTL - that should work out of the box, you just need to wait a little for components to load (wait for content inside lazy loaded regions or just wait for a promise)

Well, the whole point is to deal with @loadable/component, Suspense and lazy with dynamic imports. We are using enzyme. I see that the very ugly example above works, I override Lazy and transform import to require.

I will take a look to the following example to see if I can make it working. Otherwise, I'll go with the ugly solution. Thanks for your help.

https://github.com/timarney/react-app-rewired/issues/328#issuecomment-498836392.

stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

felixcatto commented 1 year ago

There is something wrong with this package export. Seems it provides different exports in ESM and CJS. In ESM default is loadable function, but in CJS default is an object with another default prop. So you need to write loadable.default(() => import(...)).

2023-05-27_06-58

2023-05-27_06-55

2023-05-27_06-56

I fixed it by writing

import rawLoadable from '@loadable/component';
const loadable = typeof rawLoadable === 'function' ? rawLoadable : rawLoadable.default;
stale[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

revenokanton commented 12 months ago

Finally found rather simple solution which works for me without mocking:

import { render,screen } from '@testing-library/react';
import LoadableComponent from './LoadableComponent';

test('it renders', async () => {
  const name = "LoadableComponent"

  render(<LoadableComponent name={name} />);

  expect(await screen.findByText(name)).toBeInTheDocument();
});

LoadableComponent file:

import loadable from '@loadable/component';

const LoadableComponent = loadable(
  () => import('your-library-path'),
);

export default LoadableComponent;
vladkostevich commented 12 months ago

@revenokanton dude, it's amazing!!!