Next.js: dynamic import not working in test environment

Created on 9 Oct 2018  Â·  38Comments  Â·  Source: vercel/next.js

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

Most helpful comment

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.)

All 38 comments

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

Hi! I can take a look at his đź‘€

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

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 ?

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

@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.

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

@Lidofernandez I've followed your same flow.

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

@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

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 => (

);
});```

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)

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.)

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).

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

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.

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

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?

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?

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.

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!

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.

@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?

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

This used to work for me without any change to the code or .babelrc, but [email protected] breaks my tests again. (see https://github.com/zeit/next.js/issues/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).

@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"
      ],
    }
  }
}

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

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.

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

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.

@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

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).

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.

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 ?

same issue here and the dynamic imports returns this

image

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

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lixiaoyan picture lixiaoyan  Â·  3Comments

timneutkens picture timneutkens  Â·  3Comments

wagerfield picture wagerfield  Â·  3Comments

flybayer picture flybayer  Â·  3Comments

knipferrc picture knipferrc  Â·  3Comments