Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamic import not working in test environment #5416

Closed
giovannigiordano opened this issue Oct 9, 2018 · 41 comments
Closed

dynamic import not working in test environment #5416

giovannigiordano opened this issue Oct 9, 2018 · 41 comments

Comments

@giovannigiordano
Copy link
Contributor

Bug report

Describe the bug

I have upgraded next.js from version 6 to 7, and running tests with jest now exits with the following:
TypeError: require.resolveWeak is not a function

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Clone https://github.com/giovannigiordano/next.js/tree/master/examples/with-jest
  2. Install dependencies
  3. Run yarn test
  4. See error

Expected behavior

I expect testing dynamic import as would work as for version 6

Screenshots

https://i.imgur.com/i3aw7Eg.png

System information

  • OS: macOS Mojave 10.14
  • Version of Next.js: 7.0.1
@timneutkens
Copy link
Member

Sounds like you have to transpile them yourself in the test environment using the babel plugin.

@timneutkens timneutkens added help wanted good first issue Easy to fix issues, good for newcomers labels Oct 9, 2018
@HeroProtagonist
Copy link

Hi! I can take a look at his 👀

@giovannigiordano
Copy link
Contributor Author

@HeroProtagonist actually I have mocked next/dynamic and added babel-plugin-transform-dynamic-import.

@revskill10
Copy link
Contributor

I'm using "next": "^7.0.2-canary.7", and have no issue with the with-jest example.
My .babelrc is just

{
  "presets": ["next/babel"]
}

Could you try the latest version ?

@giovannigiordano
Copy link
Contributor Author

@revskill10 I hope you have tried my fork because even if I use 7.0.2-canary.7 it doesn't work.

@shYkiSto
Copy link
Contributor

shYkiSto commented Oct 25, 2018

@HeroProtagonist did you get a chance to have a look?

I'm having the same issue and would like to get some support here, as it seems to me that the changes to dynamic imports in Next.js@7 are not compatible with the jest env. And no instructions given, nor there's an example/integration in the Next.js repo itself

The issue is reproducible in the repo @giovannigiordano provided.

@Lidofernandez
Copy link

hi @giovannigiordano can you elaborate a bit more about how you mocked the dynamic/import ? I can't figure it out yet :(

babelrc

{
  "presets": ["next/babel"],
  "plugins": [
    ["styled-components", { "ssr": true, "displayName": true, "preprocess": false }]
  ],
  "env": {
    "test": {
      "plugins": ["transform-dynamic-import"]
    }
  }
}

test

jest.mock('next/dynamic', () => () => 'DynamicDrawer');

file

import dynamic from 'next/dynamic';

const DynamicDrawer = dynamic({
  loader: () => import('./SimpleDrawer/index'),
  Loading: () => null,
});

@giovannigiordano
Copy link
Contributor Author

@Lidofernandez I've followed your same flow.

@Lidofernandez
Copy link

@giovannigiordano the problem I'm trying to test my dynamic component with jest and snapshot. It should render the dynamic component onClick but it nothing happens:

import React from 'react';
import { mount } from 'enzyme';

import navigationList from '../../../../data/navigation';

import Navigaton from '../../../../../src/components/Navigation';

describe('Navigaton', () => {
  let navigation;
  let navigationToHtml;
  beforeEach(() => {
    navigation = mount(<Navigaton
      tabs={navigationList}
    />);
    navigationToHtml = navigation.getDOMNode();
  });

  afterEach(() => {
    navigation = () => {};
    navigationToHtml = '';
  });

  it('should render', () => {
   // this test is passing because its without the drawer
    expect(navigationToHtml).toMatchSnapshot();
  });

  it('should render the drawer on click', () => {
   // this test is failing because nothing is being generated on click
    navigation.find('i.mdc-top-app-bar__navigation-icon').simulate('click');
    expect(navigationToHtml).toMatchSnapshot();
  });
});

@giovannigiordano
Copy link
Contributor Author

@Lidofernandez if you mock the dynamic import substituting the implementation with an empty function, the module will return undefined so it won't show in the snapshot

@Lidofernandez
Copy link

indeed sorry, here there is my solution:

jest.mock('next/dynamic', () => () => {
  const SimpleDrawer =
    // eslint-disable-next-line global-require
    require('../../../../../src/components/Navigation/SimpleDrawer').default;

  return props => (
    <SimpleDrawer {...props} />
  );
});```

@SBoudrias
Copy link
Contributor

FWIW, here's the fix we've implemented inside jest.setup.js on my team. The advantage is that it's set up globally for all our tests.

// Manually mock next/dynamic as the next.js (7.0.2) babel plugin will compile to Webpack
// lazy imports (require.resolveWeak) who're conflicting with the Node module system.
jest.mock('next/dynamic', () => () => {
    const DynamicComponent = () => null;
    DynamicComponent.displayName = 'LoadableComponent';
    DynamicComponent.preload = jest.fn();
    return DynamicComponent;
});

The behavior should be exactly the same as before as the dynamic weren't ever processed (even inside snapshots if you have some)

@exogen
Copy link
Contributor

exogen commented Dec 14, 2018

heya, we've recently published a package that also mocks next/dynamic, but actually lets you render the resulting dynamic components: https://github.com/FormidableLabs/jest-next-dynamic

The idea is:

  1. Import the real next/dynamic, but wrap it and capture the Loadable component's preload function.
  2. Add all preload initializers to a queue.
  3. Export a preloadAll function that will flush the queue.

From there, you just need to call beforeAll(preloadAll) and it will be as if the dynamic components were all already available – meaning they actually render, appear in snapshots instead of "loading…", etc. This is exactly how you'd do it with react-loadable's built-in Loadable.preloadAll method.

Similar to the approach above, by mocking the next/dynamic module and flushing a queue of every dynamic component, you don't have to track down and export every single one in order to get at its preload method. :)

Hope that helps someone!

(* Since the default Jest environment simulates a DOM environment, even if you get a handle on Loadable's built-in preloadAll method, it won't actually work due to this line which skips enqueuing the preload initializers – since in a Jest DOM environment, window will be defined.)

@exogen
Copy link
Contributor

exogen commented Dec 14, 2018

Oh and as far as advising the Next.js team on a potential built-in solution for this:

  1. I think there are already accommodations made in the Babel preset when the NODE_ENV is set to test – maybe the custom import Babel plugin is another thing to switch out there? Currently the import plugin pretty much depends on webpack, but it's rare to use webpack for tests.
  2. Sync up with the latest react-loadable implementation, which enqueues preload initializers even on the client (no typeof window check).
  3. Expose preloadAll as an export of next/dynamic. Right now it's possible to call preload on individual dynamic() results, but preloadAll isn't exposed as an API anywhere (unless you reach into dist folders to find loadable.js).

@awnali
Copy link

awnali commented Dec 17, 2018

this is how i hacked it,

const TodosList =
  process.env.NODE_ENV === "test"
    ? require("../components/TodosList").default
    : dynamic({
        loader: () => import("../components/TodosList"),
        loading: () => <p>loading ...</p>,
        ssr: true
      });

@damusnet
Copy link
Contributor

Here is my custom solution using Jest node modules mocking:

/* ./__mocks__/next/dynamic.js */

import React from 'react';

const mockReact = React;

let componentName = 'DynamicComponent';

export const __setComponentName = data => {
  componentName = data;
};

const DynamicComponent = () => ({ children, ...rest }) =>
  mockReact.createElement(componentName, rest, children);

export default DynamicComponent;

Then in your test:

import React from 'react';
import TestRenderer from 'react-test-renderer';

import MyComponent from '..';

describe('<MyComponent />', () => {

  require('next/dynamic').__setComponentName('NameOfTheDynamicComponentForTheSnapshot');

  it('should work as usual', () => {
    const component = TestRenderer.create(<MyComponent />);
    expect(component.toJSON()).toMatchSnapshot();
  });
});

No dependency or babel plugin needed.

It will render the snapshot as if you had called jest.mock('../DynamicComponent', () => 'DynamicComponent'); on a regular component and lets you cover that component in another test.

@timneutkens
Copy link
Member

I wonder if this is solved in canary with the import to node transform, cc @lfades

@jhoffmcd
Copy link
Contributor

Just wanted to give a quick link to a CodeSandbox repro:

https://codesandbox.io/s/4j3lw8lj20

If you fork this and open a new terminal and run yarn jest you can see the dynamic import fail. Maybe there is a config step missing here?

@ahnkee
Copy link

ahnkee commented Apr 22, 2019

Just wanted to give a quick link to a CodeSandbox repro:

https://codesandbox.io/s/4j3lw8lj20

If you fork this and open a new terminal and run yarn jest you can see the dynamic import fail. Maybe there is a config step missing here?

@jhoffmcd I kept tinkering with it, took an approach of having separate tsconfig from this repo: https://github.com/deptno/next.js-typescript-starter-kit.

Forked/revised sandbox (https://codesandbox.io/s/949zpj8yw) seems to be working. I don't know if this is the right/best approach but... it works?

@suppayami
Copy link

Hi, Is there any update regarding this problem? Using jest-next-dynamic is a bit hacky and it does not work fine with shallow render in test.

@cksharma11
Copy link

this is how i hacked it,

const TodosList =
  process.env.NODE_ENV === "test"
    ? require("../components/TodosList").default
    : dynamic({
        loader: () => import("../components/TodosList"),
        loading: () => <p>loading ...</p>,
        ssr: true
      });

Changing the code to pass the tests seems not a good idea. But anyway it worked!

@lfades
Copy link
Member

lfades commented May 30, 2019

Here's a working sandbox: https://codesandbox.io/s/nextdynamictestingissue-siuvx using a fork from @jhoffmcd sandbox.

The only change I did was to .babelrc:

{
  "presets": [
    "next/babel",
    "@zeit/next-typescript/babel"
  ],
  "env": {
    "test": {
      "plugins": [
        "babel-plugin-dynamic-import-node"
      ]
    }
  }
}

Using [email protected], it also works in the latest canary. Please let me know if it's working.

@axelinternet
Copy link

@lfades I'm having the same issue as originally posted but can't seem to fix it by adding jest-next-dynamic, babel-plugin-dynamic-import-node and using the .babelrc setup you suggested.

I upgraded to 8.1.0 without any difference, still getting the original TypeError: require.resolveWeak is not a function error. Any ideas on how I can continue to debug this?

@lfades
Copy link
Member

lfades commented Jun 26, 2019

@axelinternet I did a change to .babelrc, can you test it again ? 🙏

@damusnet
Copy link
Contributor

This used to work for me without any change to the code or .babelrc, but [email protected] breaks my tests again. (see #7872).

{
  "presets": ["next/babel"],
  "env": {
    "test": {
      "plugins": ["babel-plugin-dynamic-import-node"]
    }
  }
}

makes them pass again but seems unfortunate as it means that tests and code don't use the same config anymore. And using babel-plugin-dynamic-import-node in all environments seems to disable the dynamic loading altogether (i.e. everything works but no chunks are loaded).

@lfades
Copy link
Member

lfades commented Jul 16, 2019

@damusnet Can you see test it with the .babelrc in this codesandbox 🙏

it looks like this:

{
  "presets": [
    "next/babel"
  ],
  "env": {
    "test": {
      "presets": [
        [
          "next/babel",
          {
            "transform-runtime": {
              "corejs": false
            }
          }
        ]
      ],
      "plugins": [
        "babel-plugin-dynamic-import-node"
      ],
    }
  }
}

@Timer Timer added this to the 9.0.x milestone Jul 17, 2019
@macrozone
Copy link

macrozone commented Aug 9, 2019

also got the same error in next 9.0.

I use typescript and added "babel-plugin-dynamic-import-node", to the plugins in test-env.

my babelrc looks like this:

{
  plugins: ["babel-plugin-styled-components"],
  env: {
    development: {
      presets: ["next/babel", "@zeit/next-typescript/babel", "@babel/preset-typescript"],
      plugins: ["ts-optchain"],
    },
    production: {
      presets: ["next/babel", "@zeit/next-typescript/babel", "@babel/preset-typescript"],
      plugins: ["ts-optchain"],
    },
    test: {
      presets: [
        [
          "next/babel",
          {
            "preset-env": {
              modules: "commonjs",
            },
          },
        ],
        "@zeit/next-typescript/babel",
        "@babel/preset-typescript",
      ],

      plugins: ["babel-plugin-dynamic-import-node", "ts-optchain"],
    },
  },
}

EDIT: using https://github.com/FormidableLabs/jest-next-dynamic solved the issue for me

@chanatorns
Copy link

chanatorns commented Sep 13, 2019

I have met this error too and try above solutions except using jest-next-dynamic.
I still can't solve the problem.
Now, I manage to evade this error by use jest's options --coverage.
When I test with this option it will not get the error.

@Timer
Copy link
Member

Timer commented Oct 10, 2019

This was fixed recently in Next.js 8.1 or 9, I cannot recall exactly.

@Timer Timer closed this as completed Oct 10, 2019
@Timer Timer removed good first issue Easy to fix issues, good for newcomers help wanted labels Oct 10, 2019
@ellsclytn
Copy link

Hi @Timer, I'm not sure this issue should be closed. I'm still facing this issue (same error as in the first post). I've made a minimal reproducible version here: https://github.com/ellsclytn/next-with-jest

A clone, install all deps, and yarn test/npm run test will show it. This particular version has all dependencies updated to their latest at the time of writing.

@tbergquist-godaddy
Copy link

tbergquist-godaddy commented Dec 17, 2019

@Timer, could you please provide a working example of this, since I am using [email protected] and I still get TypeError: require.resolveWeak is not a function

edit: I found the answer on stack overflow

@mulholo
Copy link
Contributor

mulholo commented Dec 17, 2019

edit: I found the answer on stack overflow

I'm not sure this is really a solution. The jest-next-dynamic lib is working for me but I feel this is something that should be working out of the box for Next (and the team appears to think so too).

@tbergquist-godaddy
Copy link

I feel this is something that should be working out of the box for Next (and the team appears to think so too).

Sure, it would be great to have them provide a working example for us.

@Timer Timer removed this from the backlog milestone Jan 3, 2020
@Mohsan1995
Copy link

Since the release 9.1.2, I have this error in all of my test js with a dynamic component I have this error:
TypeError: LoadableComponent.preload is not a function.

Did you have this problem ?

@x5engine
Copy link

same issue here and the dynamic imports returns this

image

@Emiliano-Bucci
Copy link

Are there any news on this issue? After upgrading to next js 10 i start to get again TypeError: require.resolveWeak is not a function

@lfades
Copy link
Member

lfades commented Nov 5, 2020

@Emiliano-Bucci Feel free to open a new issue with a reproduction that we can take a look at 🙏

@Emiliano-Bucci
Copy link

@lfades Done, thanks! -> #18855

@raulbarriga
Copy link

same issue here and the dynamic imports returns this

image

Same issue here trying to dynamically import a framer-motion component or popmotion as well. Same Typeof error console logged. Don't know what's going on. Was trying to convert import { component } from "package" using dynamic imports.

@keithpickering
Copy link

@SBoudrias This worked for me even in 2021. Anyone who actually needs to test the result of a dynamic import will probably need a different solution though.

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests