Writing tests for your applications is as important as writing the code itself. It helps you catch annoying bugs, and makes your code more maintainable and easy to understand.

We are going to attempt writing tests for a simple React Tic Tac Toe app using Jest npm package developed by Facebook as well as Enzyme testing utility created by AirBnb. You can follow the official React documentation tutorial on building basic Tic Tac Toe game, or just use the final code provided under the same link.

Before we start, in case you don’t have yarn package manager installed, I recommend installing it by running the following command in your Terminal:

npm install -g yarn

Next in order to be able to test separate components we will have to modify this app’s structure and move each component into a separate folder named accordingly.

Create a new Components folder inside your /src folder with the following structure:

Don’t forget to add export default to each of your components, include imports for React and child components reused within each parent component.

Your index.js should basically only render our main container component Game to the React DOM:

import React from 'react';

import ReactDOM from 'react-dom';

import Game from './Components/Game/game'

import './index.css'; // ======================================== ReactDOM.render(<Game />, document.getElementById("root"));

Assuming you followed the instructions in documentation and used create-react-app package to create your new app, you should know that jest testing environment is already included in that package. Installing jest separately will break your tests and cause the following error:

TypeError: environment.setup is not a function

Enzyme however requires a separate installation, so run:

yarn add enzyme enzyme-adapter-react-16 react-test-renderer

The adapter will also need to be configured, by creating a new file called setUpTests.js, and inserting the following content into it:

import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; configure({ adapter: new Adapter() });

One last step before we can finally start writing our tests. In each of the Components folders aside from the component file create a test files that should be called board.test.js, game.test.js, and square.test.js accordingly. You can learn more about test filename conventions here.

Your /src folder structure should look like this now:

Let’s start with most simple test for each component to see if they all render without an error:

// Game/game.test.js

import React from 'react'

import Game from './game'

import {shallow} from 'enzyme' it('renders without crashing', () => {

shallow(<Game />);

}); // Board/board.test.js

import React from 'react'

import Board from './board'

import {shallow} from 'enzyme' it('renders without crashing', () => {

shallow(<Board />);

}); // Square/square.test.js

import React from 'react'

import Square from './square'

import {shallow} from 'enzyme' it('renders without crashing', () => {

shallow(<Square/>);

});

Note that shallow rendering is used for isolated unit tests, however if you want to create some full rendering tests to ensure the components integrate correctly Enzyme package provides mount function as well.

Run tests by npm test in your Terminal. Right now you should see 2 tests passing for Game and Square, but 1 failing for Board:

TypeError: Cannot read property '0' of undefined

As mentioned above we are using isolated unit tests and our Board component requires a squares prop to be passed to it in order to render correctly. In order to see if this component renders correctly let’s pass the squares prop to it explicitly in our tests:

import React from 'react'

import Board from './board'

import {shallow} from 'enzyme' it('renders without crashing', () => {

let squares = Array(9).fill(null)

shallow(<Board squares={squares}/>);

});

All 3 tests should be passing now! Note that there is no need to restart your tests every time a change has been made. While running npm test command, jest testing environment will be started automatically with --watch prefix which will literally watch files for changes and restart tests related to those changed files.

Next let’s test that on click of a board square an onClick event fires up. Since Board renders Square components and passes onClick event to them as a prop we will need to render our Board component as well as it’s children Square components, so we will use mount() function provided by Enzyme instead of shallow(). We will also have to create a “fake” onClick event that we would usually pass from Game component to Board using jest mock function jest.fn() . We will also take advantage of Enzyme’s find() selector to find the component we are going to simulate click using simulate(). And, finally, we will see if the onClick was actually called and with what arguments by using Jest’s toBeCalledWith().

import React from 'react'

import Board from './board'

import {shallow, mount} from 'enzyme' it('renders without crashing', () => {

let squares = Array(9).fill(null)

shallow(<Board squares={squares}/>);

}); it('calls onClick event on click of a board square', () =>{

let squares = Array(9).fill(null)

const onClick = jest.fn();

let wrapper = mount(<Board squares={squares} onClick={onClick}/>);

wrapper.find('button.square').first().simulate('click');

expect(onClick).toBeCalledWith(0)

})

Note that you’ll need to import mount function on top of your file. Also we are expecting 0 to be the argument with which onClick event should be called, since we are simulating click on the first square of the board, which has a 0 index in the array of squares.

Check your terminal to see that this test now also passes!

With our next test we’ll check if game status renders correctly. For now we will start with a simple check of “Next Player:” and will make sure that before the game starts the next player is X and after the first move it changes to O. We will have to mount all our components in this case again, so add mount to your Enzyme import.

import React from 'react'

import Game from './game'

import {shallow, mount} from 'enzyme' it('renders without crashing', () => {

shallow(<Game />);

}); it('renders game status correctly', () => {

const wrapper = mount(<Game/>)

const firstPlayer = wrapper.find('div.game-info').children().first().text()

expect(firstPlayer).toEqual('Next player: X') const button = wrapper.find('button.square').first()

button.simulate('click')

const secondPlayer = wrapper.find('div.game-info').children().first().text()

expect(secondPlayer).toEqual('Next player: O')

})

In this case we are utilizing toEqual() method provided by jest to find out if the text content of the particular element (div.game-info) is what we expect it to be.

One last thing we have to add to our status check is declaring the winner when the game is over. We will have to simulate clicks on squares, specifying the square number using Enzyme’s .at(index) method that should return a wrapper around the node at a given index of the current wrapper (<Game />). Since we already click once on the first square for our first half of the test to work, we’ll continue taking turns starting from turn 2. So let’s update our test with the following content:

it('renders game status correctly', () => {

const wrapper = mount(<Game/>)

const firstPlayer = wrapper.find('div.game-info').children().first().text()

expect(firstPlayer).toEqual('Next player: X') const button = wrapper.find('button.square').first()

button.simulate('click')

const secondPlayer = wrapper.find('div.game-info').children().first().text()

expect(secondPlayer).toEqual('Next player: O') //player 2

const turn2 = wrapper.find('button.square').at(1)

turn2.simulate('click')

//player 1

const turn3 = wrapper.find('button.square').at(4)

turn3.simulate('click')

//player 2

const turn4 = wrapper.find('button.square').at(5)

turn4.simulate('click')

//player 1

const turn5 = wrapper.find('button.square').at(8)

turn5.simulate('click')



const winner = wrapper.find('div.game-info').children().first().text()

expect(winner).toEqual('Winner: X')

})

If you followed correctly all of your tests should now pass! In case you think you missed something, here is a link to my Github repo with final code.

This blogpost provided a short overview of how Jest and Enzyme testing environments work with React, and should be enough to get you started. Follow the links to the official documentation below to learn even more:

Jest

Enzyme