24

The Next.js dynamic() HOC components aren't really straightforward to tests. I have 2 issues right now;

  • First jest is failing to compile dynamic imports properly (require.resolveWeak is not a function - seems to be added by next babel plugin)
  • Second I can't get good coverage of the modules logic; looks like it's simply not run when trying to render a dynamic component.
Simon Boudrias
  • 42,953
  • 16
  • 99
  • 134
  • any luck? @Simon Boudrias – garrettmac Feb 11 '18 at 00:00
  • I'm also interested in this. Seems like the only way to do it is to either expose a mocked require.resolveWeak from jest, or to have a custom babel plugin which will not use the handler-imports plugin logic that next js uses. See https://github.com/zeit/next.js/blob/2d8c19a450b21df4e9f6ab8fe61c2272a0b9ac9f/server/build/babel/plugins/handle-import.js for the plugin that changes the imports into requrie.resolveWeak – Vlad Nicula Apr 24 '18 at 15:59
  • Went with mocking next/dynamic... ```jest.mock('next/dynamic', () => () => 'Dynamic')``` – damian Oct 05 '18 at 12:53
  • can you add an example for mocking the dynamic import? I have to test this: const DynamicDrawer = dynamic({ loader: () => import('./SimpleDrawer/index'), loading: () => null, }); but I'm getting this error: Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s – Lido Fernandez Nov 16 '18 at 20:22

6 Answers6

12

Let's assume we have a component like this (using a dynamic import):

import dynamic from 'next/dynamic';

const ReactSelectNoSSR = dynamic(() => import('../components/select'), {
    loading: () => <Input />,
    ssr: false
});

export default () => (
    <>
        <Header />
        <ReactSelectNoSSR />
        <Footer />
    </>
);

The dynamic import support offered by Next.js does not expose a way to preload the dynamically imported components in Jest’s environment. However, thanks to jest-next-dynamic, we can render the full component tree instead of the loading placeholder.

You'll need to add babel-plugin-dynamic-import-node to your .babelrc like so.

{
  "plugins": ["babel-plugin-dynamic-import-node"]
}

Then, you can use preloadAll() to render the component instead of the loading placeholder.

import preloadAll from 'jest-next-dynamic';
import ReactSelect from './select';

beforeAll(async () => {
    await preloadAll();
});

Source

leerob
  • 2,876
  • 1
  • 18
  • 38
8

You can add the following to your Jest setup ex: setupTests.ts

jest.mock('next/dynamic', () => () => {
    const DynamicComponent = () => null;
    DynamicComponent.displayName = 'LoadableComponent';
    DynamicComponent.preload = jest.fn();
    return DynamicComponent;
});
Smakosh
  • 1,034
  • 12
  • 13
8

The following will load the required component. You can also use similar approach to load all components before hand.

jest.mock('next/dynamic', () => ({
  __esModule: true,
  default: (...props) => {
    const dynamicModule = jest.requireActual('next/dynamic');
    const dynamicActualComp = dynamicModule.default;
    const RequiredComponent = dynamicActualComp(props[0]);
    RequiredComponent.preload
      ? RequiredComponent.preload()
      : RequiredComponent.render.preload();
    return RequiredComponent;
  },
}));
2

Although a hacky solution, what I did was to simply mock next/dynamic by extracting the import path and returning that import:

jest.mock('next/dynamic', () => ({
  __esModule: true,
  default: (...props) => {
    const matchedPath = /(.)*(\'(.*)\')(.)*/.exec(props[0].toString());
    if (matchedPath) return require(matchedPath[3]);
    else return () => <></>;
  },
}));
Usman Mani
  • 67
  • 1
  • 8
  • How would that work? You are passing an anonymous function to next/dynamic so you can't extract the path from it – chrisd08 Dec 27 '21 at 16:14
1

Here is an easy fix, which:

  • Doesn't require any 3rd party libraries
  • Still renders the dynamic component (e.g. snapshots will work)

SOLUTION:

  1. Mock the next/dynamic module, by creating a mock file in __mocks__/next/dynamic.js Jest docs
  2. Copy and paste the following code:
const dynamic = (func) => {
    const functionString = func.toString()
    const modulePath = functionString.match(/"(.*?)"/)[1]

    const namedExport = functionString.match(/mod\.(.+?(?=\)))/)
    const componentName = namedExport ? namedExport[1] : 'default'

    return require(modulePath)[componentName]
}

export default dynamic

  1. The above code assumes that you are using the dynamic() function for default and named exports as documented in the Next.js docs. I.e.:
// default
const DynamicHeader = dynamic(() =>
  import('../components/header')
)

// named export
const DynamicComponent = dynamic(() =>
  import('../components/hello').then((mod) => mod.Hello)
)

That's it! Now dynamic imports should work in all your test suites :)

Marnix.hoh
  • 1,556
  • 1
  • 15
  • 26
0

If you're having the first issue with Next8, you can mock the dynamic import with this:

jest.mock('next-server/dynamic', () => () => 'Dynamic');

For reference, see:

https://spectrum.chat/next-js/general/with-jest-and-dynamic-imports-broken~25905aad-901e-41d8-ab3e-9f97eeb51610?m=MTU1MzA5MTU2NjI0Nw==

https://github.com/zeit/next.js/issues/6187#issuecomment-467134205

Excellence Ilesanmi
  • 3,295
  • 1
  • 18
  • 17