Video Tutorial How to test?
In this chapter I suggest you discover together how to test your React components using the Jest library and the Testing Library. Testing makes sure that the components work as expected and also avoids introducing bugs when doing a refactoring.
Test Environment
With create-react-app
If you use the template create-react-app
you don't have to do much since the test environment is already set up for you. Just launch the command npm run test
to start the tests. You can take inspiration from the file App.test.js
to see what a default test looks like.
Without create-react-app
However if you use react in a project that does not use this template it is important to be able to configure things yourself. To test the code we will need several elements
- Jest, which will serve as a test framework (test runner + assertions)
- Babel, which will convert the JSX code into JavaScript code which can be understood by Node (via presets
@ babel / preset-env
and@ babel / preset-react
). - Testing Library, which will offer us helpers to easily mount / dismount our elements and which will add assertions on the DOM.
So we start by installing the different libraries
npm i jest @ types / jest babel-jest @ babel / preset-env @ babel / preset-react @ testing-library / dom @ testing-library / jest-dom @ testing-library / react --save-dev
We then create our babel configuration via a file babel.config.js
at the root of our project:
module.exports = {
presets: ('@ babel / preset-env', '@ babel / preset-react')
}
We also configure jest via a file jest.config.js
to add the DOM assertions
module.exports = {
setupFilesAfterEnv: (
'./src/setupTest.js'
)
}
And in the file setupTest.js
we charge extend expect
import '@ testing-library / jest-dom / extend-expect'
Now you can start your tests using the command npx jest
.
Write your first test
Test a component
To write a test, all you have to do is mount a component and then see that the structure is what you expect (we will often test for the presence of an element or text). Even if we manipulate the DOM it is not necessary to have recourse to a browser because our tests will be based on jsdom an implementation of standards which works directly on NodeJS. However, for more specific cases you may need a tool like jest-puppeteer to interact with a more real environment.
import {Modal} from './Modal'
import {render, fireEvent} from '@ testing-library / react'
import React from 'react'
import {screen} from '@ testing-library / dom'
test ("The title should appear", function () {
render ( null}> Hello )
const title = screen.getByText ('Hello folks')
expect (title) .toBeInTheDocument ()
})
test ('The closing callback is called when clicking on x', function () {
const mockClose = jest.fn ()
render (Hello )
const close = document.body.querySelector ("(aria-label = 'Close')")
fireEvent.click (close)
expect (mockClose.mock.calls.length) .toBe (1)
})
test ('The closing callback is called with escape', function () {
const mockClose = jest.fn ()
render (Hello )
fireEvent.keyDown (document, {key: 'Escape'})
expect (mockClose.mock.calls.length) .toBe (1)
})
test ("The closing callback is not called with a key other than escape", function () {
const mockClose = jest.fn ()
render (Hello )
fireEvent.keyDown (document, {key: 'Enter'})
expect (mockClose.mock.calls.length) .toBe (0)
})
Test a hook
Hooks are used to represent state changes and can sometimes contain logic that we will want to test. Unfortunately, a hook cannot be called directly and we will need a special environment to be able to execute them. You can use @ testing-library / react-hooks
for that.
npm i @ testing-library / react-hooks react-test-renderer --save-dev
Then just use the methods renderHook ()
and act ()
when you interact with your custom hook.
import {useToggle} from "./hooks"
import {renderHook, act} from '@ testing-library / react-hooks'
// The hook works like this
// const (visible, toggleVisible) = useToggle (false)
test ('toggleHook', function () {
const {result} = renderHook (() => useToggle (false))
// result.current will contain the return of the hook
expect (result.current (0)). toBeFalsy ()
act (() => result.current (1) ())
expect (result.current (0)). toBeTruthy ()
act (() => result.current (1) ())
expect (result.current (0)). toBeFalsy ()
})