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:
- Node.js (v4 or higher), and
- create-react-app.
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 typet.is()
, - 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,npm run build
— creates a production-ready build of our application, concatenating and compressing our assets,npm test
— 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.