// app.jsimport React from'react'import{ withRouter }from'react-router'import{ Link, Route, Switch }from'react-router-dom'constAbout=()=><h1>You are on the about page</h1>constHome=()=><h1>You are home</h1>constNoMatch=()=><h1>404 Not Found</h1>const LocationDisplay =withRouter(({ location })=>(<divdata-testid="location-display">{location.pathname}</div>))functionApp(){return(<div><Linkto="/">Home</Link><Linkto="/about">About</Link><Switch><Routeexactpath="/"component={Home}/><Routepath="/about"component={About}/><Routecomponent={NoMatch}/></Switch><LocationDisplay/></div>)}export{ LocationDisplay, App }
// app.test.jsimport React from'react'import{ Router }from'react-router-dom'import{ createMemoryHistory }from'history'import{ render, fireEvent }from'@testing-library/react'import'@testing-library/jest-dom/extend-expect'import{ LocationDisplay, App }from'./app'test('full app rendering/navigating',()=>{const history =createMemoryHistory()const{ container, getByText }=render(<Routerhistory={history}><App/></Router>)// verify page content for expected route// often you'd use a data-testid or role query, but this is also possibleexpect(container.innerHTML).toMatch('You are home')
fireEvent.click(getByText(/about/i))// check that the content changed to the new pageexpect(container.innerHTML).toMatch('You are on the about page')})test('landing on a bad page shows 404 page',()=>{const history =createMemoryHistory()
history.push('/some/bad/route')const{ getByRole }=render(<Routerhistory={history}><App/></Router>)expect(getByRole('heading')).toHaveTextContent('404 Not Found')})test('rendering a component that uses withRouter',()=>{const history =createMemoryHistory()const route ='/some-route'
history.push(route)const{ getByTestId }=render(<Routerhistory={history}><LocationDisplay/></Router>)expect(getByTestId('location-display')).toHaveTextContent(route)})
Reducing boilerplate
You can use the wrapper option to wrap a MemoryRouter around the
component you want to render (MemoryRouter works when you don't need access
to the history object itself in the test, but just need the components to be
able to render and navigate).
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 filefunctionrenderWithRouter(
ui,{
route ='/',
history =createMemoryHistory({ initialEntries:[route]}),}={}){constWrapper=({ children })=>(<Routerhistory={history}>{children}</Router>)return{...render(ui,{ wrapper: Wrapper }),// 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,}}
// app.test.jstest('landing on a bad page',()=>{const{ container }=renderWithRouter(<App/>,{
route:'/something-that-does-not-match',})expect(container.innerHTML).toMatch('No match')})test('rendering a component that uses withRouter',()=>{const route ='/some-route'const{ getByTestId }=renderWithRouter(<LocationDisplay/>,{ route })expect(getByTestId('location-display')).toHaveTextContent(route)})