https://github.com/scottrippey/next-router-mock
  
  
    Mock implementation of the Next.js Router 
    https://github.com/scottrippey/next-router-mock
  
        Last synced: 6 months ago 
        JSON representation
    
Mock implementation of the Next.js Router
- Host: GitHub
- URL: https://github.com/scottrippey/next-router-mock
- Owner: scottrippey
- License: mit
- Created: 2021-01-09T20:10:58.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2025-02-10T21:43:41.000Z (9 months ago)
- Last Synced: 2025-04-03T19:47:55.064Z (7 months ago)
- Language: TypeScript
- Size: 1.46 MB
- Stars: 408
- Watchers: 4
- Forks: 41
- Open Issues: 18
- 
            Metadata Files:
            - Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
 
Awesome Lists containing this project
README
          
# `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`](https://github.com/remix-run/react-router/blob/main/docs/router-components/memory-router.md).
Tested with NextJS v13, v12, v11, and v10.
Install via NPM: `npm install --save-dev next-router-mock`
**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Usage with Jest](#usage-with-jest)
    - [Jest Configuration](#jest-configuration)
    - [Jest Example](#jest-example)
- [Usage with Storybook](#usage-with-storybook)
    - [Storybook Configuration](#storybook-configuration)
    - [Storybook Example](#storybook-example)
- [Compatibility with `next/link`](#compatibility-with-nextlink)
    - [Example: `next/link` with React Testing Library](#example-nextlink-with-react-testing-library)
    - [Example: `next/link` with Enzyme](#example-nextlink-with-enzyme)
    - [Example: `next/link` with Storybook](#example-nextlink-with-storybook)
- [Dynamic Routes](#dynamic-routes)
- [Sync vs Async](#sync-vs-async)
- [Supported Features](#supported-features)
  - [Not yet supported](#not-yet-supported)
# Usage with Jest
### Jest Configuration
For unit tests, the `next-router-mock` module can be used as a drop-in replacement for `next/router`:
```js
jest.mock('next/router', () => require('next-router-mock'));
```
You can do this once per spec file, or you can [do this globally using `setupFilesAfterEnv`](https://jestjs.io/docs/configuration/#setupfilesafterenv-array).
### Jest Example
In your tests, use the router from `next-router-mock` to set the current URL and to make assertions.
```jsx
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 (
     router.push(href)}>
      The current route is: "{router.asPath}"
    
  );
}
describe('next-router-mock', () => {
  it('mocks the useRouter hook', () => {
    // Set the initial url:
    mockRouter.push("/initial-path");
    
    // Render the component:
    render();
    expect(screen.getByRole('button')).toHaveTextContent(
      '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:
```js
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**](#example-nextlink-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 `` component.  
```jsx
// 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 = () => (
  
    Not Active
    Active
  
);
```
> 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:
- `url` (`string` or `object`) sets the current route's URL
- `async` enables async mode, if necessary (see "Sync vs Async" for details)
- Events:
  - `onPush(url, { shallow })`
  - `onReplace(url, { shallow })`
  - `onRouteChangeStart(url, { shallow })`
  - `onRouteChangeComplete(url, { shallow })`
# Compatibility with `next/link`
To use `next-router-mock` with `next/link`, you must use a `` to wrap the test component.
### Example: `next/link` with React Testing Library
When rendering, simply supply the option `{ wrapper: MemoryRouterProvider }`
```jsx
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(
    Example Link, 
    { 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 }`
```jsx
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(
    Example Link, 
    { 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 `` component (with optional `url` set).
```jsx
// 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 = () => (
  
    Example Link
  
);
```
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](https://storybook.js.org/docs/react/writing-stories/decorators) for details).  
Global example:
```
// .storybook/preview.js
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider';
export const decorators = [
  (Story) => 
]; 
```
# 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:
```typescript
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:
```jsx
it('next/link can be tested too', async () => {
  render(Example Link);
  fireEvent.click(screen.getByText('Example Link'));
  await waitFor(() => {
    expect(singletonRouter).toMatchObject({
      asPath: '/example?foo=bar',
      pathname: '/example',
      query: { foo: 'bar' },
    });
  });
});
```
# Supported Features
- `useRouter()`
- `withRouter(Component)`
- `router.push(url, as?, options?)`
- `router.replace(url, as?, options?)`
- `router.route`
- `router.pathname`
- `router.asPath`
- `router.query`
- Works with `next/link` (see Jest notes)
- `router.events` supports:
  - `routeChangeStart(url, { shallow })`
  - `routeChangeComplete(url, { shallow })`
  - `hashChangeStart(url, { shallow })`
  - `hashChangeComplete(url, { shallow })`
## Not yet supported
PRs welcome!  
These fields just have default values; these methods do nothing.
- `router.isReady`
- `router.basePath`
- `router.isFallback`
- `router.isLocaleDomain`
- `router.locale`
- `router.locales`
- `router.defaultLocale`
- `router.domainLocales`
- `router.prefetch()`
- `router.back()`
- `router.beforePopState(cb)`
- `router.reload()`
- `router.events` not implemented:
  - `routeChangeError`
  - `beforeHistoryChange`