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 ()
})