NextJS useRouter() returns null in unit test with Storybook add-on
Asked Answered
N

1

6

I'm running into an issue when integrating my stories into my unit tests using Jest & React Testing Library in a NextJS app. In Storybook, my NextJS components render without issue with the Storybook Addon Next Router working as expected. However, in my Jest test file, my test throws an error because useRouter() returns null. I believe I've setup all the addons correctly.

My setup:

The problem:

I've setup all the files according to the docs. The story renders fine inside Storybook and the useRouter() works thanks to the Storybook Next addon. However, the composeStories() function from @storybook/testing-react seems to fail to properly initialize the Next router Provider from the first addon. My unit test fails with the following error:

Test suite failed to run
TypeError: Cannot read property 'locale' of null

And points to the following line inside my component:

// Events.tsx
const { locale = 'en' } = useRouter();

This is my test file:

// Events.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Events.stories'


const { WithSeasonsAndEvents } = composeStories(stories);

describe('Events Screen', ()=> {
  render(<WithSeasonsAndEvents />);
  it('renders input data', ()=> {
    // set up test
  });
});

And my stories file:

// Events.stories.tsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Events from './Events';

export default {
  title: 'Events/Events Screen',
  component: Events
} as ComponentMeta<typeof Events>;


const Template: ComponentStory<typeof Events> = (args) => <Events {...args} />;

export const WithSeasonsAndEvents = Template.bind({});
WithSeasonsAndEvents.args = {
  // many args here
};

This story renders fine in Storybook. useRouter() works as expected inside my story and I am able to use all of its properties including pathname and locale. However, for some reason the useRouter() function returns null when the composed story is being rendered by React Testing Library.

What I have tried:

// jest-setup.ts
import '@testing-library/jest-dom/extend-expect';
import { setGlobalConfig } from '@storybook/testing-react';

// Storybook's preview file location
import * as globalStorybookConfig from './.storybook/preview';

setGlobalConfig(globalStorybookConfig);
  • Verified that jest reads my setup file and finds my Storybook preview file
  • Verified both .storybook/preview.js and .storybook/main.js match the usage guide:

preview.js

// .storybook/preview.js
import '../styles/tailwind.css';
import { RouterContext } from "next/dist/shared/lib/router-context"; // next 12 https://storybook.js.org/docs/react/writing-stories/parameters

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
  nextRouter: {
    Provider: RouterContext.Provider,
    locale: 'en',
  },
}

main.js

// .storybook/main.js
module.exports = {
  "stories": [
    "../stories/**/*.stories.mdx",
    "../stories/**/*.stories.@(js|jsx|ts|tsx)",
    "../components/**/*.stories.@(js|jsx|ts|tsx|mdx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "storybook-addon-next-router",
  ],
  "framework": "@storybook/react"
}
  • Stories render fine in Storybook, all useRouter() features work, and when I console.log the useRouter() return value, I get the full suite of NextJS useRouter object properties:
// >> console.log(useRouter()); inside Storybook
{
    route: "/",
    pathname: "/",
    query: {},
    asPath: "/",
    events: {},
    isFallback: false,
    locale: 'en'
}
  • However, on the unit test, when logging the return value of useRouter(); inside my component, it returns null. Understandably, because it is null, the { locale } variable assignment is throwing an error in my unit test.
  • When logging the value of useRouter variable, both inside my Storybook preview and inside my unit test I get the following function:
// >> console.log(useRouter.toString())
function useRouter() {
    return _react.default.useContext(_routerContext.RouterContext);
}

Does anyone have any idea on what can be going wrong? I'm fairly new to Storybook but I've tried looking through GitHuB issues and online and haven't been able to solve. No idea why useRouter() returns null inside Jest if the composeStories() fn should take care of resolving my Storybook params & args. Any insight would be appreciated.

Novocaine answered 10/12, 2021 at 22:50 Comment(1)
can you check to see if the latest version of storybook-addon-next-router fixes this issue for you? if not, open an issue on the GitHub and I'll do my best to helpDaley
G
4

I was running with the same issue. I solved it by exporting WithNextRouter as a decorator on preview.js

// preview.js

import { WithNextRouter } from 'storybook-addon-next-router/dist/decorators';

export const decorators = [WithNextRouter];
Gregggreggory answered 24/3, 2022 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.