Mocking the <Redirect /> component in react-router works, but it’s imperfect because we don’t know for sure that the user will be redirected properly. Alternatively, we can render our component within a Router with a custom implementation of a history via createMemoryHistory. Then we can make assertions on that history object.
Component:
import React from 'react' import { Switch, Route, Link } from 'react-router-dom' const About = () => ( <div> <h1>About</h1> <p>You are on the about page</p> </div> ) const Home = () => ( <div> <h1>Home</h1> <p>You are home</p> </div> ) const NoMatch = () => ( <div> <h1>404</h1> <p>No match</p> </div> ) function Main() { return ( <div> <Link to="/">Home</Link> <Link to="/about">About</Link> <Switch> <Route exact path="/" component={Home} /> <Route path="/about" component={About} /> <Route component={NoMatch} /> </Switch> </div> ) } export { Main }
Test:
import React from 'react' import { render, fireEvent } from '@testing-library/react' import { createMemoryHistory } from 'history' import { Router } from 'react-router-dom' import '@testing-library/jest-dom/extend-expect' import { Main } from '../extra/main' test('main renders about and home page and I can navigate to those page', () => { const history = createMemoryHistory({ initialEntries: ['/'] }) const { getByRole, getByText } = render( <Router history={history}> <Main /> </Router>, ) expect(getByRole('heading')).toHaveTextContent(/home/i) fireEvent.click(getByText(/about/i)) expect(getByRole('heading')).toHaveTextContent(/about/i) }) test('landing on a bad page shows no match component', () => { const history = createMemoryHistory({ initialEntries: ['/no-match-router'] }) const { getByRole } = render( <Router history={history}> <Main /> </Router>, ) expect(getByRole('heading')).toHaveTextContent(/404/i) })
An improved version:
import React from 'react' import { Router } from 'react-router-dom' import { createMemoryHistory } from 'history' import { render, fireEvent } from '@testing-library/react' import { Main } from '../extra/main' import '@testing-library/jest-dom/extend-expect' // normally you'd put this logic in your test utility file so it can be used // for all of your tests. function renderRoute( ui, { route = '/', history = createMemoryHistory({ initialEntries: [route] }), ...renderOptions } = {}, ) { function Wrapper({ children }) { return <Router history={history}>{children}</Router> } return { ...render(ui, { wrapper: Wrapper, ...renderOptions, }), // adding `history` to the returned utilities to allow us // to reference it in our tests (just try to avoid using // this to test implementation details). history, } } test('main renders about and home and I can navigate to those pages', () => { const { getByRole, getByText } = renderRoute(<Main />) expect(getByRole('heading')).toHaveTextContent(/home/i) fireEvent.click(getByText(/about/i)) expect(getByRole('heading')).toHaveTextContent(/about/i) // you can use the `within` function to get queries for elements within the // about screen }) test('landing on a bad page shows no match component', () => { const { getByRole } = renderRoute(<Main />, { route: '/something-that-does-not-match', }) expect(getByRole('heading')).toHaveTextContent(/404/i) })