Mock implementation of the Next.js Router
npm install next-router-mocknext-router-mockAn 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
For usage with next/navigation jump to Usage with next/navigation Beta
- Usage with Jest
- Jest Configuration
- Jest Example
- Usage with Storybook
- Storybook Configuration
- Storybook Example
- Compatibility with next/link
- Example: next/link with React Testing Library
- Example: next/link with Enzyme
- Example: next/link with Storybook
- Dynamic Routes
- Sync vs Async
- Supported Features
- Not yet supported
- Usage with next/navigation Beta
- Usage with Jest
- Jest Configuration
- Jest Example
- Supported
- Not supported yet
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.
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 ;
};
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" },
});
});
});
`
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.
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 = () => (
);
`
> Be sure to import from a matching Next.js version:
>
> ``
> import { MemoryRouterProvider }
> from 'next-router-mock/MemoryRouterProvider/next-13.5';
> next-13.5
>
> Choose from , next-13, next-12, or next-11.
The MemoryRouterProvider has the following optional properties:
- url (string or object) sets the current route's URLasync
- enables async mode, if necessary (see "Sync vs Async" for details)onPush(url, { shallow })
- Events:
- onReplace(url, { shallow })
- onRouteChangeStart(url, { shallow })
- onRouteChangeComplete(url, { shallow })
-
To use next-router-mock with next/link, you must use a to wrap the test component.
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(
fireEvent.click(screen.getByText("Example Link"));
expect(mockRouter.asPath).toEqual("/example");
});
`
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(
wrapperComponent: MemoryRouterProvider,
});
wrapper.find("a").simulate("click");
expect(mockRouter.asPath).to.equal("/example");
});
`
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 = () => (
);
`
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) =>
];
`
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" },
});
});
`
By default, next-router-mock handles route changes synchronously. This is convenient for testing, and works for mostnext-router-mock/async
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 . 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" },
});
});
});
- useRouter()withRouter(Component)
- router.push(url, as?, options?)
- router.replace(url, as?, options?)
- router.route
- router.pathname
- router.asPath
- router.query
- next/link
- Works with (see Jest notes)router.events
- supports:routeChangeStart(url, { shallow })
- routeChangeComplete(url, { shallow })
- hashChangeStart(url, { shallow })
- hashChangeComplete(url, { shallow })
-
PRs welcome!
These fields just have default values; these methods do nothing.
- router.isReadyrouter.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
-
For unit tests, the next-router-mock/navigation module can be used as a drop-in replacement for next/navigation:
`js`
jest.mock("next/navigation", () => require("next-router-mock/navigation"));
You can do this once per spec file, or you can do this globally using setupFilesAfterEnv.
In your tests, use the router from next-router-mock to set the current URL and to make assertions.
`jsx
import mockRouter from "next-router-mock";
import { render, screen, fireEvent } from "@testing-library/react";
import { usePathname, useRouter } from "next/navigation";
jest.mock("next/navigation", () => jest.requireActual("next-router-mock/navigation"));
const ExampleComponent = ({ href = "" }) => {
const router = useRouter();
const pathname = usePathname();
return ;
};
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" },
});
});
});
``
- useRouter
- usePathname
- useParams
- useSearchParams
- Storybook
- useSelectedLayoutSegment, useSelectedLayoutSegments
- non-hook utils in next/navigation