Skip to main content

React Router

This example demonstrates React Router v6. For previous versions see below.

// app.js
import React from 'react'
import {Link, Route, Routes, useLocation} from 'react-router-dom'

const About = () => <div>You are on the about page</div>
const Home = () => <div>You are home</div>
const NoMatch = () => <div>No match</div>

export const LocationDisplay = () => {
const location = useLocation()

return <div data-testid="location-display">{location.pathname}</div>
}

export const App = () => (
<div>
<Link to="/">Home</Link>

<Link to="/about">About</Link>

<Routes>
<Route path="/" element={<Home />} />

<Route path="/about" element={<About />} />

<Route path="*" element={<NoMatch />} />
</Routes>

<LocationDisplay />
</div>
)
// app.test.js
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import '@testing-library/jest-dom'
import {App, LocationDisplay} from './app'
import {BrowserRouter, MemoryRouter} from 'react-router-dom'

test('full app rendering/navigating', async () => {
render(<App />, {wrapper: BrowserRouter})
const user = userEvent.setup()

// verify page content for default route
expect(screen.getByText(/you are home/i)).toBeInTheDocument()

// verify page content for expected route after navigating
await user.click(screen.getByText(/about/i))
expect(screen.getByText(/you are on the about page/i)).toBeInTheDocument()
})

test('landing on a bad page', () => {
const badRoute = '/some/bad/route'

// use <MemoryRouter> when you want to manually control the history
render(
<MemoryRouter initialEntries={[badRoute]}>
<App />
</MemoryRouter>,
)

// verify navigation to "no match" route
expect(screen.getByText(/no match/i)).toBeInTheDocument()
})

test('rendering a component that uses useLocation', () => {
const route = '/some-route'

// use <MemoryRouter> when you want to manually control the history
render(
<MemoryRouter initialEntries={[route]}>
<LocationDisplay />
</MemoryRouter>,
)

// verify location display is rendered
expect(screen.getByTestId('location-display')).toHaveTextContent(route)
})

Reducing boilerplate

  1. If you find yourself adding Router components to your tests a lot, you may want to create a helper function that wraps around render.
// test utils file
const renderWithRouter = (ui, {route = '/'} = {}) => {
window.history.pushState({}, 'Test page', route)

return {
user: userEvent.setup(),
...render(ui, {wrapper: BrowserRouter}),
}
}
// app.test.js
test('full app rendering/navigating', async () => {
const {user} = renderWithRouter(<App />)
expect(screen.getByText(/you are home/i)).toBeInTheDocument()

await user.click(screen.getByText(/about/i))

expect(screen.getByText(/you are on the about page/i)).toBeInTheDocument()
})

test('landing on a bad page', () => {
renderWithRouter(<App />, {route: '/something-that-does-not-match'})

expect(screen.getByText(/no match/i)).toBeInTheDocument()
})

test('rendering a component that uses useLocation', () => {
const route = '/some-route'
renderWithRouter(<LocationDisplay />, {route})

expect(screen.getByTestId('location-display')).toHaveTextContent(route)
})

Testing Library and React Router v5

// app.js
import React from 'react'
import {Link, Route, Switch, useLocation} from 'react-router-dom'

const About = () => <div>You are on the about page</div>
const Home = () => <div>You are home</div>
const NoMatch = () => <div>No match</div>

export const LocationDisplay = () => {
const location = useLocation()

return <div data-testid="location-display">{location.pathname}</div>
}

export const App = () => (
<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>

<LocationDisplay />
</div>
)

In your tests, pass the history object as a whole to the Router component. Note: React Router v5 only works with History v4, so make sure you have the correct version of history installed.

// app.test.js
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {createMemoryHistory} from 'history'
import React from 'react'
import {Router} from 'react-router-dom'
import '@testing-library/jest-dom'
import {App} from './app'

// React Router v5

test('full app rendering/navigating', async () => {
const history = createMemoryHistory()
render(
<Router history={history}>
<App />
</Router>,
)
const user = userEvent.setup()
// verify page content for expected route
// often you'd use a data-testid or role query, but this is also possible
expect(screen.getByText(/you are home/i)).toBeInTheDocument()

await user.click(screen.getByText(/about/i))

// check that the content changed to the new page
expect(screen.getByText(/you are on the about page/i)).toBeInTheDocument()
})

test('landing on a bad page', () => {
const history = createMemoryHistory()
history.push('/some/bad/route')
render(
<Router history={history}>
<App />
</Router>,
)

expect(screen.getByText(/no match/i)).toBeInTheDocument()
})