Random picture by Deanna Ritchie on Unsplash

Quick Guide to TDD in React

Following a TDD (Test-Driven Development) principles when writing a Front-end React App might seems more difficult than doing the same on Back-end side.
First, we need to somehow render our component, simulate user interaction with a browser, respond to changes in props and state and then come up with a way to test asynchronous methods triggered by a click of a button for instance.

Trying to cover all this cases in our tests often results in hard to read tests, they often depend on one another, we mock a lot and in return we have tests full of anti-patterns.

Respect your time

From what I’ve seen many programmers create working React components first, and then try to cover it with tests, just to realize that the component cannot be tested in the current implementation and it needs to be refactored. Because of that they lose patience, time and their employer’s money.

Available solutions

Fortunately for us, there are many testing libraries that can help us to address these problems. We can try rendering React components with Enzyme and mock API responses using MockAxios. However usually these libraries have so many methods and options that it might be confusing, especially for people who just start writing tests. Let’s take Enzyme for example — what’s the difference between Shallow, Mount and Render methods? And which should I use? This is not what you should be worried about when you write your tests in my opinion. It should be as straight forward as possible.

Quick Guide Project

For our Quick Guide purposes, we’re going to create a small React App in which after clicking on a button a random joke about Chuck Norris will be fetched and displayed.

No one has ever pair-programmed with Chuck Norris and lived to tell about it.

Let’s begin

Kick-off by creating a React project in CodeSandbox and then install following dependencies (jest is already preinstalled if you started from the link above):

  • axios — used for fetching data from external API
  • axios-mock-adapter — used for mocking server responses
  • react-testing-library— light, easy to use testing library for rendering, simulating actions and handling async methods — created by Kent C. Dodds
  • jest— for running the tests and creating assertions

Folder/files structure

  • src/index.js — entry point for our React app
  • src/jokeGenerator.js — our container component, fetches, controls and provides data
  • src/joke.js — simple presentation component
  • src/__tests__/jokeGenerator.test.js — contains our tests

Your first test

Each time before we create a component we will write a failing test first and then try to make it pass. Let’s start by writing test for our dummy component <Joke /> which will render a text from props.

jokeGenerator.test.js

Reading from the top — we use a render method from react-testing-library and pass <Joke/> component(does not exist at this point yet) into it. It returns an object containing few very useful methods (full list of available methods here) for example getByTestId — returns an HTML element based on data-testid as an argument.

Next, we write an expect using above method and data-testid and check if element contains the text from props and after running the tests we get:

Joke is not defined

Yep, we want it to fail! <Joke /> does not exist yet, we created an empty joke.js file so far. We wrote a test in which we can clearly see what we expect from the component to do and now our job is to make the test pass without modifying the test code. Let’s do that then:

joke.js

Now, if you did everything just like me, the test should pass :)

Second component

Our second component will be responsible for fetching a random joke after clicking a button by user, saving it in the component’s state and passing it down to our <Joke /> component. We would also like to display a default message when no joke has been loaded yet.

Obviously, we start with test first. It is a bigger component, hence we’ll be writing test step-by-step and make sure it is passing as often as possible.

jokeGenerator.test.js

We are already familiar with a render method but this time we are taking getByText from the return object. As you could have guessed the method returns an HTML Element if one exists in the DOM.

Run the tests and….

JokeGenerator is not defined

You know what to do with it:

jokeGenerator.js

Test is still failing but this time it outputs a different error:

Unable to find an element with the text: You haven’t loaded any joke yet. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Let’s quickly fix that by introducing a state to our component and displaying a default message when there is no joke in the state.

jokeGenerator.js

Tests are passing and we can move on to add new functionality. Imagine that we click on a button, default text in the component disappears to make room for “Loading…” message. Sounds pretty straight forward, right? We can test this scenario with only three lines of code!
Let’s import Simulate method first, we’re going to need that:

import { render, Simulate } from “react-testing-library”
Append it to our second test — jokeGenerator.test.js

Difference between queryByText and getByText is in what both return when the element is not found. The first one returns null and the second one throws an error message. Re-running tests:

Unable to find an element with the text: Load a random joke

We need to create a button and set onClick method which will set loading state to true.

jokeGenerator.js

Just like that the test is passing again. Now it’s time for fetching our random joke! Well… it won’t be random in our tests. We’ll mock it using MockAxios.

import * as axios from "axios"
import MockAxios from “axios-mock-adapter”

Above our tests in jokeGenerator.test.js insert this two lines of code:

Insert above all tests — jokeGenerator.test.js

First line creates a new instance of MockAxios with a random delay and the second line takes and executes callback function after running all the tests in this file and removes mocked state from axios.

At the top of our second test where we test <JokeGenerator /> component add:

Top of the second test — jokeGenerator.test.js

It mock response of any GET call done via axios. At the end of the same test:

jokeGenerator.test.js

Don’t forget to import wait:

import { render, Simulate, wait } from “react-testing-library”

wait method waits (4500ms by default) until a callback function stops throwing an error. It is being checked at 50ms intervals. Basically we’re just waiting until loading message disappears from DOM.

wait is also available as a separate npm package (react-testing-library uses it as a dependency). It was created by Łukasz Gozda Gandecki.

After all of the code modifications and running the tests we should get following fail message:

Expected the element not to be present
Received : <div>Loading…</div>

What do you think it might be? According to our test we expect loading message to not be there. Additionally, we want to fetch our joke from the API and save it to the state so that next expect passes.

jokeGenerator.js
Insert into render() method — jokeGenerator.js

Tests should pass again and we are sure that everything works as expected, are we? Notice that never have we opened our browser and verified manually if our app even works… However, thanks to how we were writing our tests (so that our tests resemble the way user uses the application) we might be almost 100% sure that our small app is simply working.

As the last piece of code let’s add this to the index.js and open the browser :)

index.js

Bonus

Because of the way we wrote our tests we can utilize them as e2e tests without adding a single line of code!
All we need to do is to remove all the lines related to MockAxios and run the tests again! They will now use a real external API. Cool, isn’t it? :)

Summary

All the code is available on the project’s CodeSandbox.
I really encourage you to get familiar with a full react-testing-library documentation. You’ll find there many more examples and use cases.

I hope you enjoyed my Quick Guide to TDD in React and that you’ve learnt something new today.