Testing with ReactDOM
Using React DOM for testing means rendering you React application the same way we render it in a real browser, by calling ReactDOM.render
with two arguments, a React.Element
(made directly by JSX in most cases) and a node
where to mount the application. A basic example of a test that renders using ReactDOM is the following
import React from 'react'
import ReactDOM from 'react-dom'
const App = () => <h1>Hello!</h1>
test('render app', () => {
const container = document.createElement('div')
ReactDOM.render(<App />, container)
})
After the ReactDOM call, we are able to make assertions of the content of the DOM by querying the container
HTML node. For example we can test that inside the DOM there's now a h1
with content Hello!
import React from 'react'
import ReactDOM from 'react-dom'
const App = () => <h1>Hello!</h1>
test('render app', () => {
const container = document.createElement('div')
ReactDOM.render(<App />, container)
+ expect(container.querySelector('h1').textContent).toBe('Hello!')
})
When we make a mess, we have to clean!
In order for the React event system to work, is important that the container
element passed to ReactDOM
gets appended to document.body
(in React all event listens are attached to document
). The example above works for a simple scenario but if the component had an event handler declared it would not work.
The correct way is:
- when we want to render a component
- create a container
div
- append this
div
todocument.body
- create a container
- when we completed the test
- unmount the react application
- clean the DOM
Doing this inside every test would be not practical or fun, we can use Jest beforeEach
and afterEach
utilities to make it simpler:
import React from 'react'
import ReactDOM from 'react-dom'
+ let container = null
+ beforeEach(() => {
+ // setup a DOM element as a render target
+ container = document.createElement('div')
+ document.body.appendChild(container)
+ })
+
+ afterEach(() => {
+ // cleanup on exiting
+ ReactDOM.unmountComponentAtNode(container)
+ container.remove()
+ container = null
+ })
const App = () => <h1>Hello!</h1>
test('render app', () => {
const container = document.createElement('div')
ReactDOM.render(<App />, container)
expect(container.querySelector('h1').textContent).toBe('Hello!')
})
The beforeEach
call creates a new container
each time and appends it to document.body
. The afterEach
call will call ReactDOM.unmountComponentAtNode(container)
which will trigger the unmount of components and all componentWillUnmount
liecycle hooks, removes the container from the DOM and set the variable back to null
. Please note that setting the container
variable to null
should not be necessary due to beforeEach
overriding it again, but this is to make sure nobody can read the state of the DOM.
Testing a stateful component
/* ######## setup code above ######## */
class Button extends React.Component {
state = {
value: 0,
}
render() {
return (
<div>
<div>
value: <span id="value">{this.state.value}</span>
</div>
<button
onClick={() =>
this.setState(state => {
return { value: state.value + 1 }
})
}
>
increment
</button>
</div>
)
}
}
test('stateful button', () => {
ReactDOM.render(<Button />, container)
const value = document.getElementById('value')
expect(value.textContent).toBe('0')
const button = document.querySelector('button')
button.dispatchEvent(new MouseEvent('click', { bubbles: true }))
expect(value.textContent).toBe('1')
})
Interacting with the button
react-dom
offers a set of utilities found in the package react-dom/test-utils
to help to interact with the React applications in a DOM environment (eg. simulating user actions). In the previous example ReactTestUtils.Simulate.click(button)
could be used instead of dispatchEvent
/* ######## setup code above ######## */
+ import { Simulate } from 'react-dom/test-utils'
test('stateful button', () => {
ReactDOM.render(<Button />, container)
const value = document.getElementById('value')
expect(value.textContent).toBe('0')
const button = document.querySelector('button')
- button.dispatchEvent(new MouseEvent('click', { bubbles: true }))
+ Simulate.click(button)
expect(value.textContent).toBe('1')
})
Author: Jaga Santagostino