You are on page 1of 5

React Testing Checklist v0.0.

1
Use it to make sure that your React components are tested thoroughly.

All examples use Jest and React Testing Library.

If you find errors in this document or have an improvment suggestion - send me


an email to satansdeer@gmail.com.

TLDR
To make it easier to manage your tests organize them using the describe
blocks.
Every if, && and cond ? x : y in your layout signals that you have a
condition. Create a separate describe block for each value that is used in
those conditions.
Each callback function requires a describe block.
Make dependencies explicit by defining them as props with default values.

How to Use This Checklist


1. Pick a component that you are going to test.
2. Go through the following sections and make sure that all the described
cases are covered with tests.

You can plan the tests before writing them using the it.todo syntax. This way
you can temporarily define only the name of the test and omit the callback part.

1 describe("ComponentName", () => {
2 describe("some aspect of the tested component", () => {
3 it.todo("satisfies specific expectations")
4 })
5 })

After you plan the tests go through them and write the expectations.

Your Component Renders Differently Depending


on Props
Use a separate describe block for each condition branch. This way we group test
cases with similar inputs.
ExampleComponent.jsx

1 import React from 'react';


2 import PropTypes from 'prop-types';
3
4 export const ExampleComponent = ({someFlagProp}) => {
5 if(someFlagProp){
6 return <>Renders only if someFlagProp is True</>
7 }
8 return <>The default render value</>
9 }
10
11 ExampleComponent.PropTypes = {
12 someFlagProp: PropTypes.bool,
13 };

ExampleComponent.spec.jsx

1 import React from 'react';


2 import { ExampleComponent } from './ExampleComponent';
3 import { render } from '@testing-library/react';
4
5 describe("ExampleComponent", () => {
6 describe("when someFlagProp is true", () => {
7 it("renders the someFlagProp variant", () => {
8 const { container } = render(<ExampleComponent someFlagProp=
{true} />)
9 expect(container.innerText).toMatch("Renders only if
someFlagProp is True")
10 })
11 })
12
13 describe("when someFlagProp is false", () => {
14 const { container } = render(<ExampleComponent />)
15 expect(container.innerText).toMatch("The default render
value")
16 })
17 })

The describe blocks that you create for each value the flag can have can contain
multiple cases. For example your component might only render a button if some
prop is true. Then you'll write the test for the click handler of this button in the
same describe block as you used to write the rendering test.
Every condition in your component's layout requires a separate describe block
in your tests. If your test ends up having too many describe blocks for your
conditions - it's a hint that your component might be doing too many things
and should be split into multiple components.

Your Component Renders Differently Depending


on State
Manually perform the actions that result in changed state, then check the
expectations.

You might have an urge to manipulate the state of your component


programmatically. React testing library doesn't allow you to do this. It forces
you to write the tests from the user perspective instead.

If your component's state changes on some user action, for example button click
- click this button in your test using the fireEvent method from @testing-
library/react and then check the expectation on component render value.

If you follow the Arrange Act Assert pattern - it will just mean that your Arrange
block will become bigger.

Also same as in previous section. Create a describe block for every condition
value in your layout.

Your Component Handles Events


Define a describe block for each event handler function.

CounterComponent.jsx

1 import React, { useState } from 'react'


2
3 export const CounterComponent = () => {
4 const [count, setCount] = useState(0)
5
6 const increment = () => {
7 setCount(count + 1)
8 }
9
10 return <button onClick={increment}>{count}</button>
11 }

CounterComponent.spec.jsx
1 import React from 'react';
2 import { CounterComponent } from './CounterComponent'
3 import { render, fireEvent, act } from '@testing-library/react'
4
5 describe("CounterComponent", () => {
6 describe("#increment", () => {
7 const { container, getByRole } = render(<CounterComponet />)
8
9 expect(container.innerHTML).toMatch("0")
10 act(() => {
11 fireEvent(getByRole("button"))
12 })
13 expect(container.innerHTML).toMatch("1")
14 })
15 })

When you perform actions that update the component state - you need to wrap
those actions into act function.

Your Component Has a Dependency That Is Hard


to Mock
Make this dependency explicit by defining it as a prop.

Bonus - it works with hooks as well.

ExampleComponent.jsx

1 import React from 'react'


2 import { render, fireEvent } from '@testing-library/react';
3 import { useCount } from './useCount'
4
5 export const CounterComponent = ({useCounterHook = useCount}) => {
6 const [count, increment] = useCounterHook()
7
8 return <button onClick={increment}>{count}</button>
9 }

ExampleComponent.spec.jsx

1 import React from 'react';


2 import { render, fireEvent } from '@testing-library/react'
3 import { CounterComponent } from './CounterComponent'
4
5 describe("CounterComponent", () => {
6 describe("on button click", () => {
7 const mockIncrement = jest.fn()
8 const mockUseCounter = () => ({
9 count: 0,
10 increment: mockIncrement
11 })
12
13 const { container, getByRole } = render(<CounterComponent
useCounterHook={mockUseCounter} />)
14
15 expect(container.innerHTML).toMatch("0")
16 act(() => {
17 fireEvent(getByRole("button"))
18 })
19 expect(mockIncrement).toHaveBeenCalled()
20 })
21 })

If your component ends up having too many "explicit-dependency" props - it


might be a hint that your component does too many things and has to be split
into multiple.

You might also like