Testing
React Router relies on React context to work. This affects how you can test your components that use our components.
Context
If you try to unit test one of your components that renders a or a
class Sidebar extends Component {
// ...
render() {
return (
<div>
<button onClick={this.toggleExpand}>
expand
</button>
<ul>
{users.map(user => (
<li>
<Link to={user.path}>
{user.name}
</Link>
</li>
))}
</ul>
</div>
)
}
}
// broken
test('it expands when the button is clicked', () => {
render(
<Sidebar/>
)
click(theButton)
expect(theThingToBeOpen)
})
// fixed!
test('it expands when the button is clicked', () => {
render(
<MemoryRouter>
<Sidebar/>
</MemoryRouter>
)
click(theButton)
expect(theThingToBeOpen)
})
That’s all there is to it.
Starting at specific routes
test('current user is active in sidebar', () => {
render(
<MemoryRouter initialEntries={[ '/users/2' ]}>
<Sidebar/>
</MemoryRouter>
)
expectUserToBeActive(2)
})
Navigating
We have a lot of tests that the routes work when the location changes, so you probably don’t need to test this stuff. But if you must, since everything happens in render, we do something a little clever like this:
import { render, unmountComponentAtNode } from 'react-dom'
import React from 'react'
import { Route, Link, MemoryRouter } from 'react-router-dom'
import { Simulate } from 'react-addons-test-utils'
// a way to render any part of your app inside a MemoryRouter
// you pass it a list of steps to execute when the location
// changes, it will call back to you with stuff like
// `match` and `location`, and `history` so you can control
// the flow and make assertions.
const renderTestSequence = ({
initialEntries,
initialIndex,
subject: Subject,
steps
}) => {
const div = document.createElement('div')
class Assert extends React.Component {
componentDidMount() {
this.assert()
}
componentDidUpdate() {
this.assert()
}
assert() {
const nextStep = steps.shift()
if (nextStep) {
nextStep({ ...this.props, div })
} else {
unmountComponentAtNode(div)
}
}
render() {
return this.props.children
}
}
class Test extends React.Component {
render() {
return (
<MemoryRouter
initialIndex={initialIndex}
initialEntries={initialEntries}
>
<Route render={(props) => (
<Assert {...props}>
<Subject/>
</Assert>
)}/>
</MemoryRouter>
)
}
}
render(<Test/>, div)
}
// our Subject, the App, but you can test any sub
// section of your app too
const App = () => (
<div>
<Route exact path="/" render={() => (
<div>
<h1>Welcome</h1>
</div>
)}/>
<Route path="/dashboard" render={() => (
<div>
<h1>Dashboard</h1>
<Link to="/" id="click-me">Home</Link>
</div>
)}/>
</div>
)
// the actual test!
it('navigates around', (done) => {
renderTestSequence({
// tell it the subject you're testing
subject: App,
// and the steps to execute each time the location changes
steps: [
// initial render
({ history, div }) => {
// assert the screen says what we think it should
console.assert(div.innerHTML.match(/Welcome/))
// now we can imperatively navigate as the test
history.push('/dashboard')
},
// second render from new location
({ div }) => {
console.assert(div.innerHTML.match(/Dashboard/))
// or we can simulate clicks on Links instead of
// using history.push
Simulate.click(div.querySelector('#click-me'), {
button: 0
})
},
// final render
({ location }) => {
console.assert(location.pathname === '/')
// you'll want something like `done()` so your test
// fails if you never make it here.
done()
}
]
})
})