How to mock useRouter? #23034
Replies: 64 comments 34 replies
-
Hi, this feature is still experimental but |
Beta Was this translation helpful? Give feedback.
-
@ijjk Hi, thank you! import { render, cleanup, waitForElement } from '@testing-library/react';
import { createRouter } from 'next/router';
import { RouterContext } from 'next-server/dist/lib/router-context';
const router = createRouter('', { user: 'nikita' }, '', {
initialProps: {},
pageLoader: jest.fn(),
App: jest.fn(),
Component: jest.fn(),
});
import UserInfo from './$user';
afterEach(cleanup);
it('Should render correctly on route: /users/nikita', async () => {
const { getByText } = render(
<RouterContext.Provider value={router}>
<UserInfo />
</RouterContext.Provider>,
);
await waitForElement(() => getByText(/Hello nikita!/i));
}); If there is more abstract way to mock query params, so I'd be able to pass actual route ( |
Beta Was this translation helpful? Give feedback.
This comment has been hidden.
This comment has been hidden.
-
@ijjk that make sense. Thank you a lot! |
Beta Was this translation helpful? Give feedback.
-
Is there any way to mock useRouter using Enzyme+Jest? I've been searching online for a bit and the only relevant results that come up is this issue. |
Beta Was this translation helpful? Give feedback.
-
I managed to mock it this way. import * as nextRouter from 'next/router';
nextRouter.useRouter = jest.fn();
nextRouter.useRouter.mockImplementation(() => ({ route: '/' })); |
Beta Was this translation helpful? Give feedback.
-
import React from 'react'
import { render } from '@testing-library/react'
import ResultsProductPage from 'pages/results/[product]'
const useRouter = jest.spyOn(require('next/router'), 'useRouter')
describe('ResultsProductPage', () => {
it('renders - display mode list', () => {
useRouter.mockImplementationOnce(() => ({
query: { product: 'coffee' },
}))
const { container } = render(
<ResultsProductPage items={[{ name: 'mocha' }]} />
)
expect(container).toMatchSnapshot()
})
}) |
Beta Was this translation helpful? Give feedback.
-
I ended up mocking it like this, I only need the jest.mock("next/router", () => ({
useRouter() {
return {
route: "/",
pathname: "",
query: "",
asPath: "",
};
},
})); |
Beta Was this translation helpful? Give feedback.
-
If anyone is here looking to mock
an example use case would be a form component that includes something like:
|
Beta Was this translation helpful? Give feedback.
-
@ijjk Has that behaviour changed in the latest version? I had to import from |
Beta Was this translation helpful? Give feedback.
-
I have the same exact problem. EDIT
Then in the tests :
This is not ideal but at least it works for me |
Beta Was this translation helpful? Give feedback.
-
FWIW this is what I've settled on:
I needed something that worked both in Storybook and in Jest. This seems to do the trick, you just set I think an official mocking solution would be lovely :) |
Beta Was this translation helpful? Give feedback.
-
@smasontst's method worked for us, but be careful with |
Beta Was this translation helpful? Give feedback.
-
I had to revise my initial implementation since I needed unique // Mocks useRouter
const useRouter = jest.spyOn(require("next/router"), "useRouter");
/**
* mockNextUseRouter
* Mocks the useRouter React hook from Next.js on a test-case by test-case basis
*/
export function mockNextUseRouter(props: {
route: string;
pathname: string;
query: string;
asPath: string;
}) {
useRouter.mockImplementationOnce(() => ({
route: props.route,
pathname: props.pathname,
query: props.query,
asPath: props.asPath,
}));
} I can now do things like: import { mockNextUseRouter } from "@src/test_util";
describe("Pricing Page", () => {
// Mocks Next.js route
mockNextUseRouter({
route: "/pricing",
pathname: "/pricing",
query: "",
asPath: `/pricing?error=${encodeURIComponent("Uh oh - something went wrong")}`,
});
test("render with error param", () => {
const tree: ReactTestRendererJSON = Renderer.create(
<ComponentThatDependsOnUseRouter />
).toJSON();
expect(tree).toMatchSnapshot();
});
}); Note the comment by @mbrowne - you'll hit the same issue with this approach, but you can split the example above into Also a BIG 👍 for an official mocking solution @timneutkens |
Beta Was this translation helpful? Give feedback.
-
For anyone who wants a globally mocked
This example below targets jest.mock("next/router", () => ({
// spread out all "Router" exports
...require.requireActual("next/router"),
// shallow merge the "default" exports with...
default: {
// all actual "default" exports...
...require.requireActual("next/router").default,
// and overwrite push and replace to be jest functions
push: jest.fn(),
replace: jest.fn(),
},
}));
// export the mocked instance above
module.exports = require.requireMock("next/router"); Now, anywhere there's an For reference, here's the {
__esModule: true,
useRouter: [Function: useRouter],
makePublicRouterInstance: [Function: makePublicRouterInstance],
default: {
router: null,
readyCallbacks: [
[Function],
[Function],
[Function],
[Function],
[Function],
[Function]
],
ready: [Function: ready],
push: [Function],
replace: [Function],
reload: [Function],
back: [Function],
prefetch: [Function],
beforePopState: [Function] },
withRouter: [Function: withRouter],
createRouter: [Function: createRouter],
Router: {
[Function: Router]
events: {
on: [Function: on],
off: [Function: off],
emit: [Function: emit]
}
},
NextRouter: undefined
}
} In addition, if you have to import { createElement } from "react";
import { mount } from "enzyme";
import { RouterContext } from "next/dist/next-server/lib/router-context";
// Important note: The RouterContext import will vary based upon the next version you're using;
// in some versions, it's a part of the next package, in others, it's a separate package
/**
* Factory function to create a mounted RouterContext wrapper for a React component
*
* @function withRouterContext
* @param {node} Component - Component to be mounted
* @param {object} initialProps - Component initial props for setup.
* @param {object} state - Component initial state for setup.
* @param {object} router - Initial route options for RouterContext.
* @param {object} options - Optional options for enzyme's mount function.
* @function createElement - Creates a wrapper around passed in component (now we can use wrapper.setProps on root)
* @returns {wrapper} - a mounted React component with Router context.
*/
export const withRouterContext = (
Component,
initialProps = {},
state = null,
router = {
pathname: "/",
route: "/",
query: {},
asPath: "/",
},
options = {},
) => {
const wrapper = mount(
createElement(
props => (
<RouterContext.Provider value={router}>
<Component { ...props } />
</RouterContext.Provider>
),
initialProps,
),
options,
);
if (state) wrapper.find(Component).setState(state);
return wrapper;
}; Example usage: import React from "react";
import withRouterContext from "./path/to/reusable/test/utils"; // alternatively you can make this global
import ExampleComponent from "./index";
const initialProps = {
id: "0123456789",
firstName: "John",
lastName: "Smith"
};
const router = {
pathname: "/users/$user",
route: "/users/$user",
query: { user: "john" },
asPath: "/users/john",
};
const wrapper = withRouterContext(ExampleComponent, initialProps, null, router);
...etc Why use this? Because it allows you to have a reusable mounted React component wrapped in a Router context; and most importantly, it allows you to call |
Beta Was this translation helpful? Give feedback.
-
Does somebody know how to mock useRouter for Cypress tests? |
Beta Was this translation helpful? Give feedback.
-
For the record I'm still having problems mocking |
Beta Was this translation helpful? Give feedback.
-
This works for me: // __mocks__/next/router.ts
import { Router } from "next/router";
export function useRouter() {
const router: Partial<Router> = {
route: "/",
pathname: "",
query: {},
asPath: "",
locale: "en-US",
locales: ["en-US", "zh-CN"],
defaultLocale: "en-US",
push: jest.fn(),
replace: jest.fn(),
};
return router;
} No other codes required in |
Beta Was this translation helpful? Give feedback.
-
Looks like that with the new version Now the import { RouterContext } from 'next/dist/shared/lib/router-context'
import Link from 'next/link'
import type { NextRouter } from 'next/router'
const createMockedRouter = (page: string): NextRouter => ({
basePath: '',
route: '/page/[page]',
pathname: '/page/[page]',
query: {
page
},
asPath: `/page/${page}`,
isLocaleDomain: false,
push: jest.fn(() => Promise.resolve(true)),
replace: jest.fn(() => Promise.resolve(true)),
reload: jest.fn(),
back: jest.fn(),
prefetch: jest.fn(() => Promise.resolve()),
beforePopState: jest.fn(),
events: {
emit: jest.fn(),
on: jest.fn(),
off: jest.fn()
},
isFallback: false,
isReady: true,
isPreview: false
})
test('should be able to change the query "page"', () => {
const { container } = render(
<RouterContext.Provider value={createMockedRouter('1')}>
<Link href={{ query: { page: '4' } }}>Page 4</Link>
</RouterContext.Provider>
)
expect(container).toContainHTML('<div><a href="/page/4">Page 4</a></div>')
}) |
Beta Was this translation helpful? Give feedback.
-
I was using storybook and I was trying to test whether the preview mode is on or off.
import { ComponentMeta, ComponentStory } from "@storybook/react";
import { RouterContext } from "next/dist/shared/lib/router-context";
import { useRouter } from "next/router";
export default {
title: "MyComponent",
component: MyComponent,
} as ComponentMeta<typeof MyComponent>;
export const Primary: ComponentStory<typeof MyComponent> = () => {
const router = useRouter();
const mockedRouter = {
...router,
isPreview: true, // mocked value
};
return (
<RouterContext.Provider value={mockedRouter}>
<MyComponent />
</RouterContext.Provider>
);
}; |
Beta Was this translation helpful? Give feedback.
-
I am using a jest + react testing library, if you guys mock the router and API calls import '@testing-library/jest-dom' // mock router const mockPush = jest.fn() // mock router // mock the api call in next js describe('LoginPage', () => { it('should render the login page', async () => {
// mock router |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Storybook cannot show a component importing I followed the way next-router-mock instructs, but it didn't work. Is there any workaround? // BackButton.tsx
'use client'
import { ChevronLeftIcon } from '@heroicons/react/outline/esm'
import { usePathname, useRouter } from 'next/navigation'
import { useEffect } from 'react'
import { Button } from '@/components'
import { event } from '@/lib'
const BackButton: React.VFC = () => {
const router = useRouter()
const toPosts = () => router.push('/posts')
const pathname = usePathname() ?? ''
useEffect(() => {
router.prefetch('/posts')
}, [router])
return (
<Button
onClick={() => {
event({
action: 'click',
category: 'back_to_post_list',
label: { path: pathname },
})
toPosts()
}}
leftIcon={<ChevronLeftIcon className="mr-2 h-4 w-4" />}
>
Go back
</Button>
)
}
export default BackButton An error on Storybook
|
Beta Was this translation helpful? Give feedback.
-
why is the
that's supposed to create auto mock for the module functions doesn't work?
worked just fine |
Beta Was this translation helpful? Give feedback.
-
I solved this a few weeks ago with "@storybook/nextjs"... but today for no reason it came back. So I reverted a lot of commits to see where it broke, cleared my browser cache, removed node_modules, it was still buggy... Then I came back in the reserve order, and it's now good. It has absolutely no sense... (even if in IT everything can be explain) So if you encounter this after fixing it. Just pray, and have patience :) |
Beta Was this translation helpful? Give feedback.
-
This is working for me. Component export const Foo: FC = () => {
const pathName = useRouter().pathname;
retrun <div>Hello</div>; UT // imports
jest.mock("next/router", () => ({
useRouter: () => {
return { pathname: "/Foo" };
},
}));
describe("Foo component Tests", () => {...}); |
Beta Was this translation helpful? Give feedback.
-
Can anyone explain why this works jest.mock('next/router', () => ({
useRouter: () => ({ push: jest.fn() }),
})); but this doesn't? import * as nextRouter from 'next/router';
jest.spyOn(nextRouter, 'useRouter').mockReturnValue({ push: jest.fn() }); I always get |
Beta Was this translation helpful? Give feedback.
-
As a different solution, we can avoid mocking the router in multiple environments (Jest, Cypress, Storybook) just by handling the error itself. in my case, I use router in a single place, and it works by doing:
This way the component is more versatile and it requires no mocking |
Beta Was this translation helpful? Give feedback.
-
So this symbol just got moved (13.5 update) from ETA: See @hoffination 's comment. If you're using Storybook, you shouldn't need to stub the next router yourself; Storybook will do it for you and you can use their API to add mocks, set the route, etc. |
Beta Was this translation helpful? Give feedback.
-
This implementation helps me to make test assertions on route pathname: describe("<Auth /> page", () => {
test("should redirect to register page", async () => {
const router: Partial<NextRouter> = {
push: jest.fn(),
prefetch: jest.fn(),
};
render(
<RouterContext.Provider value={router as NextRouter}>
<Auth />
</RouterContext.Provider>
);
await user.click(screen.getByRole("link", { name: "Register" }));
expect(router.push).toHaveBeenCalledWith(
"/auth/register",
expect.objectContaining({})
);
});
}); |
Beta Was this translation helpful? Give feedback.
-
Question about Next.js
I'm want to make sure my component renders correctly with useRouter hook (actually I'm trying to understand how new dynamic routing works), so I have code:
And what I'm trying is:
But I get an error
TypeError: Cannot read property 'query' of null
which points onconst router = useRouter();
line.P. S. I know dynamic routing is available on canary verions just for now and might change, but I get a problem with router, not with WIP feature (am I?).
Beta Was this translation helpful? Give feedback.
All reactions