Next.js: Cannot test <Link> outside of a Page on Next v9.5.0 (Enzyme + Jest)

Created on 28 Jul 2020  路  15Comments  路  Source: vercel/next.js

Bug report

Describe the bug

As of Next v9.5.0, Components which use the <Link> component cannot be tested in isolation. However, Pages which use <Link> can be tested fine.

To Reproduce

With this component:

// file: components/commonLink.tsx
import Link from "next/link";

export default function CommonLink() {
  return (
    <>
      <Link href="/other/page">
        <a>Click Me!</a>
      </Link>
    </>
  );
}

And this Test

// file: __tests__/components/commonLink.tsx
import { mount } from "enzyme";
import Component from "../../components/commonLink";

describe("navigation", () => {
  let wrapper;

  beforeEach(() => {
    wrapper = mount(<Component />);
  });

  afterEach(() => {
    wrapper.unmount();
  });

  test("renders", async () => {
    const html = wrapper.html();
    expect(html).toContain('href="/other/page">Click Me</a>');
  });
});

Produce the error:

    Error: Uncaught [TypeError: Cannot read property 'pathname' of null]
        at reportException (/Users/evan/workspace/grouparoo/grouparoo/core/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:62:24)
        at innerInvokeEventListeners (/Users/evan/workspace/grouparoo/grouparoo/core/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:333:9)
        at invokeEventListeners (/Users/evan/workspace/grouparoo/grouparoo/core/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)

...

        at Link (/Users/evan/workspace/grouparoo/grouparoo/core/node_modules/next/client/link.tsx:176:14)

...


    The above error occurred in the <Link> component:
        in Link (at commonLink.tsx:6)
        in CommonLink (created by WrapperComponent)
        in WrapperComponent

    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

      at logCapturedError (../node_modules/react-dom/cjs/react-dom.development.js:19527:21)
      at logError (../node_modules/react-dom/cjs/react-dom.development.js:19564:5)
      at update.callback (../node_modules/react-dom/cjs/react-dom.development.js:20708:5)
...

Expected behavior

The test should pass

In older versions of Next.js, this test passed.

Screenshots

N/A

System information

  • OS: OSX
  • Browser N/A
  • Version of Next.js: v9.5.0
  • Version of Node.js: v12, v14
bug 2 p1

Most helpful comment

This was just fixed in 9.5.1, please upgrade!

All 15 comments

Updated the stack trace above to include the more interesting line number:

        at Link (/Users/evan/workspace/grouparoo/grouparoo/core/node_modules/next/client/link.tsx:176:14)

This error might be interpreted as: Without a page, the Next Router doesn't load, and therefore router.pathname cannot be used. https://github.com/vercel/next.js/blob/canary/packages/next/client/link.tsx#L176

Ran into this as well. My (hopefully temporary) workaround was to add a Jest module mock for next/link (__mocks__/next/link.js) that returns a plain <a>.

ran into this as well but not in a test. i see it when i upgrade and run the app.

That's a good hack @gavinsharp!

I'm getting same issue after upgrading to v9.5.0. This error is also getting for @testing-library/react.

    Error: Uncaught [TypeError: Cannot read property 'pathname' of null]
        at reportException (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:62:24)
        at innerInvokeEventListeners (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:346:9)
        at invokeEventListeners (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:281:3)
        at HTMLUnknownElementImpl._dispatch (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:228:9)
        at HTMLUnknownElementImpl.dispatchEvent (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:101:17)
        at HTMLUnknownElement.dispatchEvent (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:207:34)
        at Object.invokeGuardedCallbackDev (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:237:16)
        at invokeGuardedCallback (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:292:31)
        at beginWork$1 (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:23203:7)
        at performUnitOfWork (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:22157:12) TypeError: Cannot read property 'pathname' of null
        at Link (/Users/sagar/fabric/copilot-web-xpm/node_modules/next/client/link.tsx:176:14)
        at renderWithHooks (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:14803:18)
        at mountIndeterminateComponent (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:17482:13)
        at beginWork (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:18596:16)
        at HTMLUnknownElement.callCallback (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:188:14)
        at innerInvokeEventListeners (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:330:27)
        at invokeEventListeners (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:281:3)
        at HTMLUnknownElementImpl._dispatch (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:228:9)
        at HTMLUnknownElementImpl.dispatchEvent (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:101:17)
        at HTMLUnknownElement.dispatchEvent (/Users/sagar/fabric/copilot-web-xpm/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:207:34)
        at Object.invokeGuardedCallbackDev (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:237:16)
        at invokeGuardedCallback (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:292:31)
        at beginWork$1 (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:23203:7)
        at performUnitOfWork (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:22157:12)
        at workLoopSync (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:22130:22)
        at performSyncWorkOnRoot (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:21756:9)
        at scheduleUpdateOnFiber (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:21188:7)
        at updateContainer (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:24373:3)
        at /Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:24758:7
        at unbatchedUpdates (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:21903:12)
        at legacyRenderSubtreeIntoContainer (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:24757:5)
        at Object.render (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:24840:10)
        at /Users/sagar/fabric/copilot-web-xpm/node_modules/@testing-library/react/dist/pure.js:100:25
        at batchedUpdates$1 (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom.development.js:21856:12)
        at act (/Users/sagar/fabric/copilot-web-xpm/node_modules/react-dom/cjs/react-dom-test-utils.development.js:929:14)
        at render (/Users/sagar/fabric/copilot-web-xpm/node_modules/@testing-library/react/dist/pure.js:96:26)
        at customRender (/Users/sagar/fabric/copilot-web-xpm/lib/testUtils/testUtils.js:28:3)
        at Object.it (/Users/sagar/fabric/copilot-web-xpm/components/SidebarNavigation/SidebarNavigation.test.js:45:45)
        at Object.asyncJestTest (/Users/sagar/fabric/copilot-web-xpm/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:100:37)
        at resolve (/Users/sagar/fabric/copilot-web-xpm/node_modules/jest-jasmine2/build/queueRunner.js:47:12)
        at new Promise (<anonymous>)
        at mapper (/Users/sagar/fabric/copilot-web-xpm/node_modules/jest-jasmine2/build/queueRunner.js:30:19)
        at promise.then (/Users/sagar/fabric/copilot-web-xpm/node_modules/jest-jasmine2/build/queueRunner.js:77:41)
        at process._tickCallback (internal/process/next_tick.js:68:7)

      at VirtualConsole.on.e (node_modules/jsdom/lib/jsdom/virtual-console.js:29:45)
      at reportException (node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:28)
      at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:346:9)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:281:3)
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:228:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:101:17)

I also run into that issue when building an app. I had to downgrade to 9.4.4 to make it work.

This could be easily fixed by providing a simple mock to the next/link component:

jest.mock('next/link', () => 'a')

This is also an issue when using Storybook (or similar) to render components outside the page context.

Right now i've fixed it by creating a custom <RouterContext.Provider> to wrap the tests/stories.

.storybook/preview.js

import React from 'react'
import { addDecorator } from '@storybook/react'
import { linkTo } from '@storybook/addon-links'
import { RouterContext } from 'next/dist/next-server/lib/router-context'
import { startCase } from 'lodash'

addDecorator((storyFn) => (
  <RouterContext.Provider
    value={{
      pathname: '/',
      basePath: '',
      push: (url, as) => {
        if (as) linkTo('Routes', as !== '/' ? startCase(as) : 'Index')()
        return Promise.resolve(true)
      },
      replace: (url, as) => {
        if (as) linkTo('Routes', as !== '/' ? startCase(as) : 'Index')()
        return Promise.resolve(true)
      },
      reload: () => {},
      prefetch: () => {},
    }}
  >
    {storyFn()}
  </RouterContext.Provider>
))

For Testing Library I had to start using a custom render method, that wraps the application with the <RouterContext>.

It works fine in v9.4.5-canary.28. But this issue happen from v9.4.5-canary.29 to v9.5.0.
So I checked out v9.4.5-canary.29 change log, I think one of the commit below is causing a problem.

  • Fix basepath browser back/forward issue: #14861
  • BasePath: Resolve links against router pathname instead of window.location: #14804

Hi, this should be resolved in v9.5.1-canary.1 of Next.js specifically this PR https://github.com/vercel/next.js/pull/15604

Thank you for the super-fast turn around!

This was just fixed in 9.5.1, please upgrade!

Hi @ijjk. I'm still having trouble with <Link> when rendered in a Material-UI v0 dropdown menu. The component gets rendered, thanks to your fix, but clicking the link gives me this error:

Unhandled Runtime Error
TypeError: Cannot read property 'push' of null

Source
../../client/link.tsx (141:9) @ linkClicked

  139 | 
  140 |   // replace state instead of push if prop is present
> 141 |   router[replace ? 'replace' : 'push'](href, as, { shallow }).then(
      |         ^
  142 |     (success: boolean) => {
  143 |       if (!success) return
  144 |       if (scroll) {

Granted, it works fine in the latest MUI (v4), but it also worked prior to Next.js v9.5. Unfortunately, we're stuck with MUI v0 for the time being.

These dropdown menus, popover components, etc. render somewhere outside the next root component, which I assume is why the router gets lost.

Is there any way I can help?

@stovmascript what is being tested when triggering onClick without the router being present? If you can provide a reproduction where triggering onClick for the Link component without the router context was working in a previous version but not in the current version we can take a closer look

Was this page helpful?
0 / 5 - 0 ratings

Related issues

swrdfish picture swrdfish  路  3Comments

rauchg picture rauchg  路  3Comments

wagerfield picture wagerfield  路  3Comments

jesselee34 picture jesselee34  路  3Comments

renatorib picture renatorib  路  3Comments