next-router-mock
next-router-mock copied to clipboard
Support Next 13 'next/navigation' router
Module not found: Error: Can't resolve 'next/dist/next-server/lib/router-context'
Must be nice to have an implementation to mock useRouter of next/navigation
Where do you get this error? Storybook? Jest?
I've done some tests with next 13, and so far things looked good. But I haven't looked at the new app infrastructure and whether next-router-mock will work with it.
@scottrippey I got the error in storybook and jest both once I moved to new app infrastructure. useRouter which was previously imported from next/router doesn't work inside app. They have introduced a useRouter which can be imported from next/navigation and it works on client side pages. Check this url
Although I managed to fix it in jest by mocking the useRouter (from navigation). I'm still trying to find a way to mock it in storybook. :/
I have the same problem but without the new app infra
I get the same thing when trying to add MemoryRouterProvider as a storybook decorator, using nextjs 13.0.2, not using the new app infrastructure.
receiving an error in storybook when using the MemoryRouterProvider
ERROR in ../../node_modules/next-router-mock/dist/MemoryRouterProvider/next-10.js 6:25-76
Module not found: Error: Can't resolve 'next/dist/next-server/lib/router-context' in ...
using nextjs 13.0.3, and NOT using the new app infrastructure.
my build is also using turborepo, so I'm not sure if this adds to issue
*** update ***
it does seem to work if I continue using
import { useRouter } from "next/router";
instead of
import { useRouter } from "next/navigation";
and I also hard code the next/dist/next-server/lib/router-context location into the next-10.js file
this might be a turborepo thing, not really sure, but hope it helps someone
Regarding MemoryRouterProvider issues:
In Next 13, they did not change any of the internal paths. So, I'm having trouble reproducing the issue. I created #66 to test the Next 13 integration, and tests are passing so far.
Can anyone create a CodeSandbox to demonstrate this issue? Did anyone upgrade from Next 12, where it was working, and then Next 13 broke? (just to verify that it's a Next 13 issue?)
I was able to resolve the issue @e-roy reported above in https://github.com/Soil-labs/eden-app-2-FrontEnd/pull/688#pullrequestreview-1207242476.
We had to move our decorators from decorator.tsx to the decorators export of preview.tsx.
We also needed to change the import to next-router-mock/MemoryRouterProvider/next-12 because it defaults to trying to load next-10. No idea why that is.
Using next-router-mock/MemoryRouterProvider/next-12 for now but new <Link/> component isnt being rendered
import { addDecorator } from '@storybook/react'; import { AppRouterContext } from 'next/dist/shared/lib/app-router-context';
addDecorator((Story) => (
<AppRouterContext.Provider>
<RootLayoutRegistry>
<Story />
</RootLayoutRegistry>
</AppRouterContext.Provider>
));
This will fix it.
So there are 2 separate issues here, hopefully this message helps people
Module not found: Error: Can't resolve 'next/dist/next-server/lib/router-context'
is solved by importing from this path instead:
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider/next-13';
I will update the documentation soon!
The problem with the
next-router-mock/MemoryRouterProviderpath is that it tries to load from./next-13and also does./next-10as a backup. This approach works fine in Jest, but Storybook, using Webpack, tries to compile both.
Supporting next/navigation
I will add support for this very soon too. I believe the API is very similar, but the Context Provider is different, so I will have to do some work to test this out. I'm leaving this issue open to track this feature.
I found a way to make next-router-mock work with next/navigation, including useSearchParams
Maybe this can help other people.
vi.mock('next/router', () => require('next-router-mock'));
vi.mock('next/navigation', () => ({
...require('next-router-mock'),
useSearchParams: () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const router = require('next-router-mock').useRouter();
const path = router.asPath.split('?')?.[1] ?? '';
return new URLSearchParams(path);
},
}));
This is a great start, thank you!
I've been playing with Next 13 lately, and it seems like the next/navigation implementation will be mostly easy, since the underlying URL logic is pretty similar.
But as I've been strapped for time, I haven't been able to implement the extra API hooks, and it might be a while before I can support the full suite of next/navigation features.
I'd be happy to accept PRs, even if they only offer "partial" navigation support for now.
@mxswat your solution is super helpful! I'm using usePathname so using your strategy I was at least able to get my tests running again with a hardcoded path.
vi.mock('next/navigation', () => ({
...require('next-router-mock'),
useSearchParams: () => {
...
},
usePathname: () => '/'
}));
I'm still looking for a solution so that I can define the path the test should see, but very happy to be moving again!
I also made a new version that supports usePathname and other
import * as mockRouter from 'next-router-mock';
const useRouter = mockRouter.useRouter;
export const MockNextNavigation = {
...mockRouter,
notFound: vi.fn(),
redirect: vi.fn().mockImplementation((url: string) => {
mockRouter.memoryRouter.setCurrentUrl(url);
}),
usePathname: () => {
const router = useRouter();
return router.asPath;
},
useSearchParams: () => {
const router = useRouter();
const path = router.query;
return new URLSearchParams(path as any);
},
};
And I use it like this,
import mockRouter from 'next-router-mock';
import { MockNextNavigation } from '@/test-mocks/next-navigation';
vi.mock('next/navigation', () => MockNextNavigation);
And very important, reset the router URL in the beforeEach
beforeEach(() => {
mockRouter.setCurrentUrl('/');
I've started a branch to implement these next/navigation hooks! I'm going to move this discussion to #103, since this thread has too many side-tracks.
Reopened for tracking purposes.
I also made a new version that supports
usePathnameand otherimport * as mockRouter from 'next-router-mock'; const useRouter = mockRouter.useRouter; export const MockNextNavigation = { ...mockRouter, notFound: vi.fn(), redirect: vi.fn().mockImplementation((url: string) => { mockRouter.memoryRouter.setCurrentUrl(url); }), usePathname: () => { const router = useRouter(); return router.asPath; }, useSearchParams: () => { const router = useRouter(); const path = router.query; return new URLSearchParams(path as any); }, };And I use it like this,
import mockRouter from 'next-router-mock'; import { MockNextNavigation } from '@/test-mocks/next-navigation'; vi.mock('next/navigation', () => MockNextNavigation);And very important, reset the router URL in the
beforeEachbeforeEach(() => { mockRouter.setCurrentUrl('/');
How can we extend the MemoryRouterProvider to include usePathName? I'm using the path on my Navbar and the value always comes null.
@scottrippey any review here?
@scottrippey any review here?
https://github.com/scottrippey/next-router-mock/pull/103#issuecomment-2192407604
PRs are welcome :)