npm install next-router-mock

Mock implementation of the Next.js Router

About next-router-mock

The npm package "next-router-mock" serves as an essential tool for developers working with Next.js applications, specifically designed to enhance testing and development environments like Storybook. This mock implementation of the Next.js Router maintains the state of the "URL" entirely in memory, ensuring that it does not interact with the actual address bar. This feature is particularly beneficial as it allows developers to simulate routing and test various components and pages without the need for a live browser environment. By using next-router-mock, developers can efficiently ensure the robustness of routing functionalities before deployment, significantly reducing potential runtime errors and enhancing the overall quality of web applications.

For developers looking to integrate next-router-mock into their projects, the process is streamlined and simple with the command "npm install next-router-mock". This installation method quickly sets up the mock router, making it immediately available for use in test suites and Storybook scenarios. The ability of next-router-mock to replicate the Next.js Router behavior without actual browser interaction is invaluable, facilitating rapid development cycles and continuous integration processes. Moreover, the package helps maintain a clean and manageable codebase, as it isolates the router testing environment from the rest of the application, preventing any unintended side effects on the actual application's routing logic.

Next-router-mock is particularly advantageous for large-scale projects and enterprise applications where testing accuracy and efficiency are paramount. The package not only supports standard routing functionalities but also handles dynamic routes and query parameters with ease, making it a versatile tool for developers aiming to create sophisticated, high-quality Next.js applications. By enabling precise control over routing mechanisms during the development and testing phases, next-router-mock plays a crucial role in building scalable, reliable, and user-friendly web applications.

More from scottrippey

scottrippey npm packages

Find the best node modules for your project.

Search npm

next-router-mock

Mock implementation of the Next...

Read more

Dependencies

Core dependencies of this npm package and its dev dependencies.

@changesets/cli, @testing-library/react, @types/jest, doctoc, jest, next, prettier, react, react-dom, react-test-renderer, rimraf, ts-jest, typescript

Documentation

A README file for the next-router-mock code repository. View Code

next-router-mock

An implementation of the Next.js Router that keeps the state of the "URL" in memory (does not read or write to the address bar). Useful in tests and Storybook. Inspired by react-router > MemoryRouter.

Tested with NextJS v13, v12, v11, and v10.

Install via NPM: npm install --save-dev next-router-mock

Table of Contents generated with DocToc

Usage with Jest

Jest Configuration

For unit tests, the next-router-mock module can be used as a drop-in replacement for next/router:

jest.mock('next/router', () => require('next-router-mock'));

You can do this once per spec file, or you can do this globally using setupFilesAfterEnv.

Jest Example

In your tests, use the router from next-router-mock to set the current URL and to make assertions.

import { useRouter } from 'next/router';
import { render, screen, fireEvent } from '@testing-library/react';
import mockRouter from 'next-router-mock';

jest.mock('next/router', () => jest.requireActual('next-router-mock'))

const ExampleComponent = ({ href = '' }) => {
  const router = useRouter();
  return (
    <button onClick={() => router.push(href)}>
      The current route is: "{router.asPath}"
    </button>
  );
}

describe('next-router-mock', () => {
  it('mocks the useRouter hook', () => {
    // Set the initial url:
    mockRouter.push("/initial-path");
    
    // Render the component:
    render(<ExampleComponent href="/foo?bar=baz" />);
    expect(screen.getByRole('button')).toHaveText(
      'The current route is: "/initial-path"'
    );

    // Click the button:
    fireEvent.click(screen.getByRole('button'));
    
    // Ensure the router was updated:
    expect(mockRouter).toMatchObject({ 
      asPath: "/foo?bar=baz",
      pathname: "/foo",
      query: { bar: "baz" },
    });
  });
});

Usage with Storybook

Storybook Configuration

Globally enable next-router-mock by adding the following webpack alias to your Storybook configuration.

In .storybook/main.js add:

module.exports = {
  webpackFinal: async (config, { configType }) => {
    config.resolve.alias = {
      ...config.resolve.alias,
      "next/router": "next-router-mock",
    };
    return config;
  },
};

This ensures that all your components that use useRouter will work in Storybook. If you also need to test next/link, please see the section Example: next/link with Storybook.

Storybook Example

In your individual stories, you might want to mock the current URL (eg. for testing an "ActiveLink" component), or you might want to log push/replace actions. You can do this by wrapping your stories with the <MemoryRouterProvider> component.

// ActiveLink.story.jsx
import { action } from '@storybook/addon-actions';
import { MemoryRouterProvider } 
  from 'next-router-mock/MemoryRouterProvider/next-13';
import { ActiveLink } from './active-link';

export const ExampleStory = () => (
  <MemoryRouterProvider url="/active" onPush={action("router.push")}>
    <ActiveLink href="/example">Not Active</ActiveLink>
    <ActiveLink href="/active">Active</ActiveLink>
  </MemoryRouterProvider>
);

Be sure to import from a matching Next.js version:

import { MemoryRouterProvider } 
  from 'next-router-mock/MemoryRouterProvider/next-13.5';

Choose from next-13.5, next-13, next-12, or next-11.

The MemoryRouterProvider has the following optional properties:

Compatibility with next/link

To use next-router-mock with next/link, you must use a <MemoryRouterProvider> to wrap the test component.

Example: next/link with React Testing Library

When rendering, simply supply the option { wrapper: MemoryRouterProvider }

import { render } from '@testing-library/react';
import NextLink from 'next/link';

import mockRouter from 'next-router-mock';
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider';

it('NextLink can be rendered', () => {
  render(
    <NextLink href="/example">Example Link</NextLink>, 
    { wrapper: MemoryRouterProvider }
  );
  fireEvent.click(screen.getByText('Example Link'));
  expect(mockRouter.asPath).toEqual('/example')
});

Example: next/link with Enzyme

When rendering, simply supply the option { wrapperComponent: MemoryRouterProvider }

import { shallow } from 'enzyme';
import NextLink from 'next/link';

import mockRouter from 'next-router-mock';
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider';

it('NextLink can be rendered', () => {
  const wrapper = shallow(
    <NextLink href="/example">Example Link</NextLink>, 
    { wrapperComponent: MemoryRouterProvider }
  );
  
  wrapper.find('a').simulate('click');
  
  expect(mockRouter.asPath).to.equal('/example')
});

Example: next/link with Storybook

In Storybook, you must wrap your component with the <MemoryRouterProvider> component (with optional url set).

// example.story.jsx
import NextLink from 'next/link';
import { action } from '@storybook/addon-actions';

import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider/next-13.5';

export const ExampleStory = () => (
  <MemoryRouterProvider url="/initial">
    <NextLink href="/example">Example Link</NextLink>
  </MemoryRouterProvider>
);

This can be done inline (as above).
It can also be implemented as a decorator, which can be per-Story, per-Component, or Global (see Storybook Decorators Documentation for details).
Global example:

// .storybook/preview.js
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider';

export const decorators = [
  (Story) => <MemoryRouterProvider><Story /></MemoryRouterProvider>
]; 

Dynamic Routes

By default, next-router-mock does not know about your dynamic routes (eg. files like /pages/[id].js). To test code that uses dynamic routes, you must add the routes manually, like so:

import mockRouter from "next-router-mock";
import { createDynamicRouteParser } from "next-router-mock/dynamic-routes";

mockRouter.useParser(createDynamicRouteParser([
  // These paths should match those found in the `/pages` folder:
  "/[id]",
  "/static/path",
  "/[dynamic]/path",
  "/[...catchAll]/path"
]));

// Example test:
it('should parse dynamic routes', () => {
  mockRouter.push('/FOO');
  expect(mockRouter).toMatchObject({
    pathname: '/[id]',
    query: { id: 'FOO' }
  });
})

Sync vs Async

By default, next-router-mock handles route changes synchronously. This is convenient for testing, and works for most use-cases.
However, Next normally handles route changes asynchronously, and in certain cases you might actually rely on that behavior. If that's the case, you can use next-router-mock/async. Tests will need to account for the async behavior too; for example:

it('next/link can be tested too', async () => {
  render(<NextLink href="/example?foo=bar"><a>Example Link</a></NextLink>);
  fireEvent.click(screen.getByText('Example Link'));
  await waitFor(() => {
    expect(singletonRouter).toMatchObject({
      asPath: '/example?foo=bar',
      pathname: '/example',
      query: { foo: 'bar' },
    });
  });
});

Supported Features

Not yet supported

PRs welcome!
These fields just have default values; these methods do nothing.