Let’s build a React app in TDD

When I learn a framework or language, the first thing I want to write is a basic test and watch it turn from red to green

Guillaume Rondon
10 min readMay 25, 2020
Photo by Zdeněk Macháček on Unsplash

In this tutorial, we will build a simple React application by following the Test Driven Development routine “red / green / refactor”. We will learn the basics of React : how to build functional components with props and states (using hooks) and the basics of React testing : shallow rendering and simulating events and using test data tables.

The app we’re building

Nothing fancy, just enough to learn something by using some mechanics of the framework. We’re building a little form with two inputs : size and weight. When the size and weight are filled, the Body Mass Index (BMI) is calculated.

The body mass index is an approximate measure of whether someone is over- or underweight, calculated by dividing their weight in kilograms by the square of their height in metres.

When the BMI is less than 18.5 or greater than 25, we’ll simply display an asterisk after the value.

A little design (optional)

Optionally, before writing the first test, we can spend a little time thinking about the needed components and data flow.

The <App> component will contain the <BmiForm> component. Inside the <BmiForm> component we will have two inputs and the <Calc> component.

When the user changes the “size” or “weight” value, we update the “size” and “weight” states in the BmiForm component and we pass them as props to the <Calc> components who will be responsible for the calculation and display of the value and alert if needed.

Environment setup

Create a new application using Create React App:

create-react-app bodymassindex

Then go to the project root directory and add enzyme:

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

We also add jest-enzyme so that we can use assertions such as expect().toHaveText(‘bar’) or expect().toExist();

yarn add jest-enzyme

By default, create-react-app is configured to use React Testing Library but we’ll stick to enzyme and shallow testing in this tutorial. To make it possible, edit the file ./src/setupTest.js and replace everything with :

setupTests.js

Now, remove all the existing code from ./src/App.test.js and add the following imports:

App.test.js

Execute the tests with yarn run test and verify that you get the following error :

yarn run test

Now let’s write the first test.

The first test: starting things up

Let’s write a simple test to check that the <App> component contains a BmiForm component.

App.test.js

See it fail:

To make it pass, we need to do two things :

  • Create a new component BmiForm (in the “components” folder)
  • Import it and use it in App.js and App.test.js
BmiForm.js
App.js
App.test.js
yarn run test

Just adding a child component to a parent component isn’t something spectacular but it’s actually something we do a lot in React so it’s a good thing to know how to test it. Here we created a wrapper with “shallow” and checked that it contains the component.

The Second Test: rendering elements

Now, let’s check that the BmiForm component contains two inputs, one for the size and one for the weight.

Create a BmiForm.test.js file and write the following test

BmiForm.test.js

See it fail :

Write enough code to make it pass :

BmiForm.test.js

Easy. Let’s do the same for the weight input then we will refactor a little.

BmiForm.test.js

While we’re green, let’s take a minute to refactor our tests. We duplicated the wrapper creation, we should extract it to a beforeEach() bloc.

BmiForm.test.js

The BmiForm component should contain one last thing, the Calc component.

Let’s add a test for it. (Here we add the import statement while the file doesn’t exist yet but we’ll create it just after the test fails)

BmiForm.test.js

Notice that this is a compilation error. The test is not even executed:

Create the component with a basic div rendering, notice the new error :

Calc.js
Red, the component doesn’t exist

Now the test is correctly executed. Let’s make it pass :

BmiForm.js
Green !

CSS Interlude

Now that we have all the structure in place and some basic tests that pass, it should be a good time to add some CSS… As I don’t want to dive into details of CSS libraries for this tutorial, I just added bulma to center things up and make the two inputs look good enough.

Playing with bulma in BmiForm.js
The app running on localhost:3000

The Third Test: implementing the logic

I took my calculator and used the formula to get my Body Mass Index.

The body mass index is an approximate measure of whether someone is over- or underweight, calculated by dividing their weight in kilograms by the square of their height in metres.

For the moment, the calculation will be in the Calc component. We may refactor it later. Let’s create a Calc.test.js file with the following content:

Here we’re passing parameters (props) to the Calc component and we read the result of a calculation in the element with id bmi. Of course, nothing is coded yet, it’s red :

Red, nothing is coded yet

This error tells us that no element with id bmi can be found in the shallow rendered component. Let’s create one to make the test pass.

Calc.js
Red, for good reasons

The tests still fail but for a good reason now: the value is empty. We could be a little silly and make it pass by adding “22.72” directly in the jsx but let’s make it pass with a real calculation:

Calc.js
Green, but ugly… Let’s refactor !

Before refactoring, the first thing we may notice if we look at the application running on localhost, is that it displays an ugly “NaN”. This is because we try to display a result even when the props are undefined. Let’s add a test for this case.

Calc.test.js
Indeed, the “NaN” is here

Now that we tested it, let’s fix it with an inline if (ternary operator).

Calc.js getting uglier
Let’s refactor this

We have a very basic ratio calculation but our very simple component is already a mess.

Let’s extract the function.

Calc.js — Still green

We could go further and create a utility file with the getBodyMassIndex function, it’s always a good thing to separate the logic and the rendering but let’s keep it like that for the moment since we won’t use this calculation anywhere else.

Before moving to the alerts functionality, we should manage the division by zero. Let’s add a test for it :

Calc.test.js

Still green, ok, great but we have a lot of duplication in the tests. Let’s refactor that.

Since we’re using two values as input and reading one value as output, we should use a data table and write only one test with “test.each

Calc.test.js
Still green

It should be easier to add the underweight and overweight test cases now.

Let’s add two rows in the data table. When the Body Mass Index is lower than 18.5 or greater than 25 we will display an asterisk next to the value:

Red as expected

Make it pass:

Calc.js : It will clearly need refactoring,
Green, let’s refactor

Ok, we implemented it, but the Calc component needs a refactoring, we have magic numbers, we call the same function three times with the same parameters and we don’t know what the meaning of this “if” is by reading the code. Let’s make it clearer while keeping the tests green.

Calc.js

Nice, this should be enough for the calculation of the result. Now let’s make the app respond to user actions, in our case, typing numbers in the input fields.

The Fourth Test: responding to user actions

Let’s reopen the BmiForm.test.js file. In a previous test, we verified that the component contained two inputs, now we will simulate user actions on them.

The goal here is to verify that when the user changes the size or weight values, they are being sent as props to the Calc component. To implement this behaviour in our BmiForm component we will use states but the test doesn’t have to verify the implementation, we only want to verify the outcomes.

BmiForm.test.js

Here we use the simulate function from enzyme to change the values of the inputs. Then we check that the props of the Calc component are the same as the input values. Note that once this test pass, we won’t need anymore the three first tests as they become redundant.

Let’s see the error message:

Red: we change the inputs but the values are not passed in the Calc props

Let’s make it pass with the useState hook.

BmiForm.js

This is pretty strait-forward, we declare states and their values are set when the inputs are changed. When calling the Calc component, we simply pass the values of those states.

Green

Alright, everything should look good. Let’s add some CSS and see how it looks in the browser.

Not bad !

Conclusion

While this is a very simple application, we covered a lot of basic React, Jest and Enzyme principles such as rendering elements, using props, managing events and states, using conditional rendering… And more importantly, we did it while maintaining a full confidence that we didn’t break anything, seeing the tests switch from red to green on each modification.

Bonus : 100% coverage is always nice

I hope this tutorial will help you exploring the world of React and Test Driven Development.

--

--

Guillaume Rondon

I like iterative development, functional programming and TDD.