Test routing in nextjs
25/05/2023 Wassim Nassour
Hi friends, this article shows how you can test your next/router in different ways. To test push or any method from next/router , I found two approaches: mocking the next/router , and creating a router context inside your test and wrapping your app with it. We will dig deep into both approaches.
If you try to run next/router in isolation, you will encounter an issue. To understand this issue, you need to know how useRouter from next/router works.
this is the internal code of useRouter as you see it just consumes the routerContext that’s setup when you run the app
To avoid this issue in our app, we have two approaches. The first is to mock next/router ,
while the second is to create the context for useRouter in the component we are testing.
The component we are testing is a simple one, with a push method from useRouter . However, with this approach, we are testing the methods of next/router . You will see that we are only checking the methods now, because we have fixed the error that useRouter throws.
const Component = () => {
const router = useRouter()
const navigateToNext = () => {
router.push('/next-page?name=test')
}
return (
<div>
<button onClick={navigateToNext}>Navigate to Other page</button>
</div>
)
}
First approach: mock useRouter from the next/router
The first approach involves mocking next/router using the jest.mock method. By creating a mock implementation of useRouter , you can simulate routing behavior in your tests. You can render your component, simulate the button click, and then verify that the push method is called with the expected URL
import Home from '../pages/index'
import { fireEvent, render, screen } from '@testing-library/react'
import { useRouter } from 'next/router'
jest.mock('next/router', () => {
const router = {
push: jest.fn(),
query: {}
}
return {
useRouter: jest.fn().mockReturnValue(router)
}
})
test('test navigation with push', () => {
render(<Home />)
const button = screen.getByRole('button', {
name: 'Navigate to Other page'
})
fireEvent.click(button)
expect(useRouter().push).toHaveBeenCalledTimes(1)
expect(useRouter().push).toHaveBeenCalledWith('/next-page?name=test')
})
Second approach: mock the context for the useRouter hook
we will create helper function that generates a mocked router object with necessary methods and values . also create MockRouterWrapper component that provides the router object as a value to the RouterContext. We can then use this wrapper in our test to render the component and simulate the button click.
import Home from '../pages/index'
import { RouterContext } from 'next/dist/shared/lib/router-context'
export function createMockRouter(router) {
return {
basePath: '',
pathname: '/',
route: '/',
query: {},
asPath: '/',
back: jest.fn(),
beforePopState: jest.fn(),
prefetch: jest.fn(),
push: jest.fn(),
reload: jest.fn(),
replace: jest.fn(),
events: {
on: jest.fn(),
off: jest.fn(),
emit: jest.fn()
},
isFallback: false,
isLocaleDomain: false,
isReady: true,
defaultLocale: 'en',
domainLocales: [],
isPreview: false,
...router
}
}
const MockRouterWrapper = ({ children, router }) => {
return <RouterContext.Provider value={router}>{children}</RouterContext.Provider>
}
Finally, after we created our Wrapper and function helper , we can once again check that the push method is called with the correct URL an.
test('test navigation with push', () => {
// create wrapper object
const router = createMockRouter()
render(<Home />, {
// pass wrapper router options
wrapper: props => <MockRouterWrapper {...props} router={router} />
})
const button = screen.getByRole('button', {
name: 'Navigate to Other page'
})
fireEvent.click(button)
expect(router.push).toHaveBeenCalledTimes(1)
expect(router.push).toHaveBeenCalledWith('/next-page?name=test')
})
In summary, both approaches allow you to focus on testing the useRouter methods. We chose to test only the push method because the main idea is to be able to test the methods from userRouter. With the function helper or the mocking version, you can check or simulate any method from useRouter