Introduction

In React codebases, you can cover a lot of ground using just unit tests because the code is mostly universal. In this tutorial, you will learn how to unit test a simple todo application built with React and Redux using AVA. We will use AVA’s modern design to write tests using the latest JavaScript syntax. After completing this tutorial, you will be familiar with some advanced testing concepts and understand how AVA works.

Prerequisites

For this tutorial, you will need the following:

Why AVA?

AVA is a modern framework, and is under active development. There are many useful features, to name a few:

Assertions are tiny, instead of expressions like expect().toBe() you can type t.is() ,

you can type , Enhanced assertion messages allow you to easier understand why your assertion failed,

There’s a fast and intelligent watch mode, and

If you have a known bug, but don’t have time to fix it straight away, you can write a failing test which won’t break CI.

There’s even an ESLint plugin available, so you can catch errors earlier and keep your tests consistent.

Installing Node.js

It’s easy to install Node.js using nvm. If you’re on macOS and have Homebrew installed, you can install nvm by running:

brew install nvm

Otherwise, run:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.0/install.sh | bash

With nvm, we can have as many versions of Node.js installed as we want, and switch between them using nvm use . To install Node v6, run:

nvm install 6

This will automatically install npm as well. We can set v6 as the default version by running:

nvm alias default 6

To test if Node.js and npm are installed, run:

node -v npm -v

This should output their version numbers.

Creating the Application

We can start building our application using create-react-app :

npm install --global create-react-app create-react-app todo-app cd todo-app

This gives us all of the tools necessary for building a very simple React application, along with the following npm scripts:

npm start — serves our application on http://localhost:3000, auto-reloads on file change and lints our JavaScript code using ESLint,

— serves our application on http://localhost:3000, auto-reloads on file change and lints our JavaScript code using ESLint, npm run build — creates a production-ready build of our application, concatenating and compressing our assets,

— creates a production-ready build of our application, concatenating and compressing our assets, npm test — runs the tests, using Jest by default,

— runs the tests, using Jest by default, npm run eject — fully exposes background configurations for Webpack, Babel and ESLint, giving us full control over the tools.

Installing and Configuring AVA

Since create-react-app uses Jest by default, we will modify our setup to use AVA instead. We can start by installing it:

npm install --save-dev ava

We can continue by modifying the test script in package.json to run AVA:

{ " name " : " todo-app " , " version " : " 0.1.0 " , " private " : true , " devDependencies " : { " ava " : " ^0.16.0 " , " react-scripts " : " 0.6.1 " }, " dependencies " : { " react " : " ^15.3.2 " , " react-dom " : " ^15.3.2 " }, " scripts " : { " start " : " react-scripts start " , " build " : " react-scripts build " , " test " : " ava " , " eject " : " react-scripts eject " } }

Even though AVA is bundled with some reasonable defaults, we have to configure it so it can parse JSX and some experimental features if needed. We’ll simply use the same preset that create-react-app uses, and that is babel-preset-react-app :

npm install --save-dev babel-preset-react-app

We’ll use this preset as our Babel configuration, and then we can start configuring AVA by telling it to use our project’s Babel configuration. Let’s add these configurations to our package.json as babel and ava keys respectively:

{ " name " : " todo-app " , " version " : " 0.1.0 " , " private " : true , " devDependencies " : { " ava " : " ^0.16.0 " , " babel-preset-react-app " : " ^0.2.1 " , " react-scripts " : " 0.6.1 " }, " dependencies " : { " react " : " ^15.3.2 " , " react-dom " : " ^15.3.2 " }, " scripts " : { " start " : " react-scripts start " , " build " : " react-scripts build " , " test " : " ava " , " eject " : " react-scripts eject " }, " babel " : { " presets " : " react-app " }, " ava " : { " babel " : " inherit " } }

babel-preset-react-app requires us to set NODE_ENV before running tests, which lets React know in which environment it’s running. We should normalize setting environment variables using cross-env. That way, it will work both on Unix and Windows operating systems. To install it, run:

npm install --save-dev cross-env

Now, let’s modify our test script to use cross-env :

{ " name " : " todo-app " , " version " : " 0.1.0 " , " private " : true , " devDependencies " : { " ava " : " ^0.16.0 " , " babel-preset-react-app " : " ^0.2.1 " , " cross-env " : " ^3.0.0 " , " react-scripts " : " 0.6.1 " }, " dependencies " : { " react " : " ^15.3.2 " , " react-dom " : " ^15.3.2 " }, " scripts " : { " start " : " react-scripts start " , " build " : " react-scripts build " , " test " : " cross-env NODE_ENV=test ava " , " eject " : " react-scripts eject " }, " babel " : { " presets " : " react-app " }, " ava " : { " babel " : " inherit " } }

AVA only parses test files, not imported modules, i.e. our application code. To fix that, we should require babel-register before running tests. To install it, run:

npm install --save-dev babel-register

Now, let’s add it to our AVA configuration, under require :

{ " name " : " todo-app " , " version " : " 0.1.0 " , " private " : true , " devDependencies " : { " ava " : " ^0.16.0 " , " babel-preset-react-app " : " ^0.2.1 " , " babel-register " : " ^6.16.3 " , " cross-env " : " ^3.0.0 " , " react-scripts " : " 0.6.1 " }, " dependencies " : { " react " : " ^15.3.2 " , " react-dom " : " ^15.3.2 " }, " scripts " : { " start " : " react-scripts start " , " build " : " react-scripts build " , " test " : " cross-env NODE_ENV=test ava " , " eject " : " react-scripts eject " }, " babel " : { " presets " : " react-app " }, " ava " : { " babel " : " inherit " , " require " : [ " babel-register " ] } }

Note on polyfills: if you’re using the transform-runtime in your application, you don’t need to do anything extra. babel-preset-react-app uses it, and inherit will cover it. If you’re using babel-polyfill instead, you will most probably need it in your tests as well, so you can prepend it to the require array.

In our application, we’ll import JavaScript (or JSON), and in src/index.js you can see that we’ll import CSS as well:

// src/index.js import React from ' react ' ; import ReactDOM from ' react-dom ' ; import App from ' ./App ' ; import ' ./index.css ' ; // <-- ReactDOM . render ( < App / > , document . getElementById ( ' root ' ) );

Node.js can’t import CSS files, so we need a way to ignore those imports. They aren’t needed in our tests anyway. There are a couple of modules which can help with this issue, for example ignore-styles, which ignores stylesheets, images and videos. To install ignore-styles, run:

npm install --save-dev ignore-styles

Now we can append it to our AVA require list:

{ " name " : " todo-app " , " version " : " 0.1.0 " , " private " : true , " devDependencies " : { " ava " : " ^0.16.0 " , " babel-preset-react-app " : " ^0.2.1 " , " babel-register " : " ^6.16.3 " , " cross-env " : " ^3.0.0 " , " ignore-styles " : " ^5.0.1 " , " react-scripts " : " 0.6.1 " }, " dependencies " : { " react " : " ^15.3.2 " , " react-dom " : " ^15.3.2 " }, " scripts " : { " start " : " react-scripts start " , " build " : " react-scripts build " , " test " : " cross-env NODE_ENV=test ava " , " eject " : " react-scripts eject " }, " babel " : { " presets " : " react-app " }, " ava " : { " babel " : " inherit " , " require " : [ " babel-register " , " ignore-styles " ] } }

Rendering components requires a DOM, so we will use jsdom to create one. We will do that in a separate setup file, which we will also require before running tests. Let’s install it first:

npm install --save-dev jsdom

Now we can set it up in our new setup file. Let’s name it src/test-setup.js :

// src/test-setup.js const jsdom = require ( ' jsdom ' ). jsdom ; global . document = jsdom ( ' <body></body> ' ); global . window = document . defaultView ; global . navigator = window . navigator ;

We need AVA to require this file before running the tests, so we’ll append it to the require array:

{ " name " : " todo-app " , " version " : " 0.1.0 " , " private " : true , " devDependencies " : { " ava " : " ^0.16.0 " , " babel-preset-react-app " : " ^0.2.1 " , " babel-register " : " ^6.16.3 " , " cross-env " : " ^3.0.0 " , " ignore-styles " : " ^5.0.1 " , " jsdom " : " ^9.5.0 " , " react-scripts " : " 0.6.1 " }, " dependencies " : { " react " : " ^15.3.2 " , " react-dom " : " ^15.3.2 " }, " scripts " : { " start " : " react-scripts start " , " build " : " react-scripts build " , " test " : " cross-env NODE_ENV=test ava " , " eject " : " react-scripts eject " }, " babel " : { " presets " : " react-app " }, " ava " : { " babel " : " inherit " , " require " : [ " babel-register " , " ignore-styles " , " ./src/test-setup " ] } }

Running the First Test

First, we should modify our sample test file, src/App.test.js , to use AVA instead of Jest:

// src/App.test.js import test from ' ava ' ; import React from ' react ' ; import ReactDOM from ' react-dom ' ; import App from ' ./App ' ; test ( ' renders without crashing ' , t => { const div = document . createElement ( ' div ' ); ReactDOM . render ( < App / > , div); });

To test if our setup is working correctly, run:

npm test

You should get something like the following output:

To run AVA in the watch mode, run:

npm test -- --watch

Notice the additional -- , this is required for passing options to commands behind npm scripts.

Continuous Testing on Semaphore CI

Lastly, let’s add continuous testing to our application using Semaphore CI. First, push your repository to GitHub or Bitbucket, so Semaphore can run the tests. Next, sign up for a free Semaphore account, if you haven’t already. After you’ve confirmed your email, we can create a new project.

Select your cloud account and, if prompted, authorize GitHub or Bitbucket, depending on where your repository is.

You’ll be welcomed with a list of your open source projects. From there find and select your repository. Now, select the branch that your code is on ( master by default) and Semaphore will start analyzing your project, figuring out the language, test command etc. Because some of our modules like cross-env are using advanced JavaScript features, we should bump the Node.js version to v4 or greater. Other options are fine.

Press the button “Build With These Settings”, and you’re done with setting up Semaphore to run your tests.

Conclusion

create-react-app helps with eliminating some of the choice fatigue associated with starting new React projects, and helps you focus on writing actual application code. It allows for just enough customization, in our case switching to AVA.

AVA is one of the most ambitious and advanced testing framework currently available. It has excellent cohesive documentation (including translations) and a very responsive team behind it.

In this tutorial, we learned one way to start a React application and set up testing. We overcame some of the initial challenges, like unifying Babel configurations, setting up a DOM in case we need it, and dealing with CSS imports. Finally, after setting up Semaphore to run our tests, we are ready to start unit testing our application code.

You can find all of the code in this GitHub repository, and if you have any questions and comments, feel free to leave them in the section below.