Skip to content

Commit df3df4a

Browse files
author
Kent C. Dodds
committed
docs(tests): add some more examples in tests
1 parent 7cdc26d commit df3df4a

File tree

4 files changed

+199
-1
lines changed

4 files changed

+199
-1
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ components. It provides light utility functions on top of `react-dom` and
4545
* [`flushPromises`](#flushpromises)
4646
* [`render`](#render)
4747
* [More on `data-testid`s](#more-on-data-testids)
48+
* [Examples](#examples)
4849
* [FAQ](#faq)
4950
* [Other Solutions](#other-solutions)
5051
* [Guiding Principles](#guiding-principles)
@@ -144,6 +145,17 @@ one of the practices this library is intended to encourage.
144145
Learn more about this practice in the blog post:
145146
["Making your UI tests resilient to change"](https://blog.kentcdodds.com/making-your-ui-tests-resilient-to-change-d37a6ee37269)
146147

148+
## Examples
149+
150+
You'll find examples of testing with different libraries in
151+
[the test directory](https://github.com/kentcdodds/react-testing-library/blob/master/src/__tests__).
152+
Some included are:
153+
154+
* [`react-redux`](https://github.com/kentcdodds/react-testing-library/blob/master/src/__tests__/react-redux.js)
155+
* [`react-router`](https://github.com/kentcdodds/react-testing-library/blob/master/src/__tests__/react-router.js)
156+
157+
Feel free to contribute more!
158+
147159
## FAQ
148160

149161
**How do I update the props of a rendered component?**

package.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,15 @@
2626
"dependencies": {},
2727
"devDependencies": {
2828
"axios": "^0.18.0",
29+
"history": "^4.7.2",
2930
"kcd-scripts": "^0.36.1",
3031
"react": "^16.2.0",
3132
"react-dom": "^16.2.0",
32-
"react-transition-group": "^2.2.1"
33+
"react-redux": "^5.0.7",
34+
"react-router": "^4.2.0",
35+
"react-router-dom": "^4.2.2",
36+
"react-transition-group": "^2.2.1",
37+
"redux": "^3.7.2"
3338
},
3439
"peerDependencies": {
3540
"react-dom": "*"

src/__tests__/react-redux.js

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import React from 'react'
2+
import {createStore} from 'redux'
3+
import {Provider, connect} from 'react-redux'
4+
import {render, Simulate} from '../'
5+
6+
// counter.js
7+
class Counter extends React.Component {
8+
increment = () => {
9+
this.props.dispatch({type: 'INCREMENT'})
10+
}
11+
12+
decrement = () => {
13+
this.props.dispatch({type: 'DECREMENT'})
14+
}
15+
16+
render() {
17+
return (
18+
<div>
19+
<h2>Counter</h2>
20+
<div>
21+
<button onClick={this.decrement} data-testid="decrementer">
22+
-
23+
</button>
24+
<span data-testid="count-value">{this.props.count}</span>
25+
<button onClick={this.increment} data-testid="incrementer">
26+
+
27+
</button>
28+
</div>
29+
</div>
30+
)
31+
}
32+
}
33+
34+
// normally this would be:
35+
// export default connect(state => ({count: state.count}))(Counter)
36+
// but for this test we'll give it a variable name
37+
// because we're doing this all in one file
38+
const ConnectedCounter = connect(state => ({count: state.count}))(Counter)
39+
40+
// app.js
41+
function reducer(state = {count: 0}, action) {
42+
switch (action.type) {
43+
case 'INCREMENT':
44+
return {
45+
count: state.count + 1,
46+
}
47+
case 'DECREMENT':
48+
return {
49+
count: state.count - 1,
50+
}
51+
default:
52+
return state
53+
}
54+
}
55+
56+
// normally here you'd do:
57+
// const store = createStore(reducer)
58+
// ReactDOM.render(
59+
// <Provider store={store}>
60+
// <Counter />
61+
// </Provider>,
62+
// document.getElementById('root'),
63+
// )
64+
// but for this test we'll umm... not do that :)
65+
66+
// Now here's what your test will look like:
67+
68+
// this is a handy function that I normally make available for all my tests
69+
// that deal with connected components.
70+
// you can provide initialState or the entire store that the ui is rendered with
71+
function renderWithRedux(
72+
ui,
73+
{initialState, store = createStore(reducer, initialState)} = {},
74+
) {
75+
return {
76+
...render(<Provider store={store}>{ui}</Provider>),
77+
// adding `store` to the returned utilities to allow us
78+
// to reference it in our tests (just try to avoid using
79+
// this to test implementation details).
80+
store,
81+
}
82+
}
83+
84+
test('can render with redux with defaults', () => {
85+
const {queryByTestId} = renderWithRedux(<ConnectedCounter />)
86+
Simulate.click(queryByTestId('incrementer'))
87+
expect(queryByTestId('count-value').textContent).toBe('1')
88+
})
89+
90+
test('can render with redux with custom initial state', () => {
91+
const {queryByTestId} = renderWithRedux(<ConnectedCounter />, {
92+
initialState: {count: 3},
93+
})
94+
Simulate.click(queryByTestId('decrementer'))
95+
expect(queryByTestId('count-value').textContent).toBe('2')
96+
})
97+
98+
test('can render with redux with custom store', () => {
99+
// this is a silly store that can never be changed
100+
const store = createStore(() => ({count: 1000}))
101+
const {queryByTestId} = renderWithRedux(<ConnectedCounter />, {
102+
store,
103+
})
104+
Simulate.click(queryByTestId('incrementer'))
105+
expect(queryByTestId('count-value').textContent).toBe('1000')
106+
Simulate.click(queryByTestId('decrementer'))
107+
expect(queryByTestId('count-value').textContent).toBe('1000')
108+
})

src/__tests__/react-router.js

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from 'react'
2+
import {withRouter} from 'react-router'
3+
import {Link, Route, Router, Switch} from 'react-router-dom'
4+
import {createMemoryHistory} from 'history'
5+
import {render, Simulate} from '../'
6+
7+
const About = () => <div>You are on the about page</div>
8+
const Home = () => <div>You are home</div>
9+
const NoMatch = () => <div>No match</div>
10+
11+
const LocationDisplay = withRouter(({location}) => (
12+
<div data-testid="location-display">{location.pathname}</div>
13+
))
14+
15+
function App() {
16+
return (
17+
<div>
18+
<Link to="/" data-testid="home-link">
19+
Home
20+
</Link>
21+
<Link to="/about" data-testid="about-link">
22+
About
23+
</Link>
24+
<Switch>
25+
<Route exact path="/" component={Home} />
26+
<Route path="/about" component={About} />
27+
<Route component={NoMatch} />
28+
</Switch>
29+
<LocationDisplay />
30+
</div>
31+
)
32+
}
33+
34+
// Ok, so here's what your tests might look like
35+
36+
// this is a handy function that I would utilize for any component
37+
// that relies on the router being in context
38+
function renderWithRouter(
39+
ui,
40+
{route = '/', history = createMemoryHistory({initialEntries: [route]})} = {},
41+
) {
42+
return {
43+
...render(<Router history={history}>{ui}</Router>),
44+
// adding `history` to the returned utilities to allow us
45+
// to reference it in our tests (just try to avoid using
46+
// this to test implementation details).
47+
history,
48+
}
49+
}
50+
51+
test('full app rendering/navigating', () => {
52+
const {container, queryByTestId} = renderWithRouter(<App />)
53+
// normally I'd use a data-testid, but just wanted to show this is also possible
54+
expect(container.innerHTML).toMatch('You are home')
55+
const leftClick = {button: 0}
56+
Simulate.click(queryByTestId('about-link'), leftClick)
57+
// normally I'd use a data-testid, but just wanted to show this is also possible
58+
expect(container.innerHTML).toMatch('You are on the about page')
59+
})
60+
61+
test('landing on a bad page', () => {
62+
const {container} = renderWithRouter(<App />, {
63+
route: '/something-that-does-not-match',
64+
})
65+
// normally I'd use a data-testid, but just wanted to show this is also possible
66+
expect(container.innerHTML).toMatch('No match')
67+
})
68+
69+
test('rendering a component that uses withRouter', () => {
70+
const route = '/some-route'
71+
const {queryByTestId} = renderWithRouter(<LocationDisplay />, {route})
72+
expect(queryByTestId('location-display').textContent).toBe(route)
73+
})

0 commit comments

Comments
 (0)