Until now, in unit tests, react router match params were retrieved as props of component. So testing a component considering some specific match, with specific url parameter
If using the enzyme
library, I found a much less verbose way to solve the problem (using this section from the react-router-dom
docs):
import React from 'react'
import { shallow } from 'enzyme'
import { MemoryRouter } from 'react-router-dom'
import Navbar from './Navbar'
it('renders Navbar component', () => {
expect(
shallow(
<MemoryRouter>
<Navbar />
</MemoryRouter>
)
).toMatchSnapshot()
})
I am trying to get if the push
function in useHistory
is called by doing that but I can't get the mocked function calls...
const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => ({
push: mockHistoryPush,
}),
}));
fireEvent.click(getByRole('button'));
expect(mockHistoryPush).toHaveBeenCalledWith('/help');
It says that mockHistoryPush
is not called when the button has onClick={() => history.push('/help')}
I looked at the tests for hooks in the react-router
repo and it looks like you have to wrap your component inside a MemoryRouter
and Route
. I ended up doing something like this to make my tests work:
import {Route, MemoryRouter} from 'react-router-dom';
...
const renderWithRouter = ({children}) => (
render(
<MemoryRouter initialEntries={['blogs/1']}>
<Route path='blogs/:blogId'>
{children}
</Route>
</MemoryRouter>
)
)
Hope that helps!
The way I ended up solving it was by mocking the hooks in my tests using jest.mock:
// TeamPage.test.js
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'), // use actual for all non-hook parts
useParams: () => ({
companyId: 'company-id1',
teamId: 'team-id1',
}),
useRouteMatch: () => ({ url: '/company/company-id1/team/team-id1' }),
}));
I use jest.requireActual
to use the real parts of react-router-dom for everything except the hooks I'm interested in mocking.
In your component use hooks as below
import {useLocation} from 'react-router';
const location = useLocation()
In your test spy on reactRouter Object as below
import routeData from 'react-router';
const mockLocation = {
pathname: '/welcome',
hash: '',
search: '',
state: ''
}
beforeEach(() => {
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation)
});
If you're using react-testing-library
for testing, you can get this mock to work like so.
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: () => ({ state: { email: 'school@edu.ng' } }),
}));
export const withReduxNRouter = (
ui,
{ store = createStore(rootReducer, {}) } = {},
{
route = '/',
history = createMemoryHistory({ initialEntries: [ route ] }),
} = {}
) => {
return {
...render(
<Provider store={store}>
<Router history={history}>{ui}</Router>
</Provider>
),
history,
store,
};
};
You should have mocked react-router-dom
before it has been used to render your component.
I'm exploring ways to make this reusable