Unit testing is the procedure during which individual parts of a program are tested to determine whether they work as expected.
Whether you plan to follow a test-driven design (TDD) approach, or simply want to verify that your program works each time you make a bunch of changes, it is generally accepted that unit testing is a good practice.
What unit testing is not, is a substitute for writing good code—you'll hardly ever get 100% test coverage, so a passing test does not necessarily mean that your code is bug-free.
In this tutorial, we'll be creating a simple REST API in Node.js Express and use Mocha/Chai to verify that the API works as expected.
Express REST API
Our API will have a single endpoint /colors
, that we can use to GET
a list of colors and POST
(insert) new values.
Create a file index.js
to write our application:
'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const PORT = 8080;
const HOST = 'localhost';
const DEFAULT_COLORS = ['RED', 'GREEN', 'BLUE'];
const app = express();
app.use(bodyParser.json({
limit: '100k',
}));
/**
* Array holding color values
*/
let colors = [].concat(DEFAULT_COLORS);
/**
* Returns a list of colors
* Response body (JSON): {results: [....]}
*/
app.get('/colors', function(req, res, next) {
res.json({
results: colors
});
});
/**
* Inserts new color in the 'colors' array
* Request body (JSON): {color: ...}
*/
app.post('/colors', function(req, res, next) {
if (req.is('application/json') && typeof req.body.color === 'string') {
let color = req.body.color.trim().toUpperCase();
if (color && colors.indexOf(color) < 0) {
colors.push(color);
// 201 Created
return res.status(201).send({
results: colors
});
}
}
res.status(400).send(); // 400 Bad Request
});
app.listen(PORT, HOST);
console.log('Listening on %s:%d...', HOST || '*', PORT);
/**
* Export the Express app so that it can be used by Chai
*/
module.exports = app;
Assuming you are fairly familiar with Express, the above code should be easy to follow.
The reason we export the express application in the last line is because we want to be able to automatically launch our server during testing and import our app instance into Chai HTTP.
Unit Testing
To create our unit tests, we will be using Mocha, Chai, and Chai HTTP modules.
Mocha is a popular JavaScript test framework—it runs test cases and reports any errors encountered during the process. However, Mocha does not have a built-in assertion library. That's where Chai comes in, which is an assertion library for Node.js. Assertions are simple statements that are always expected to evaluate to true, and if not, they throw an error. Furthermore, since we are testing API endpoints, we also need a module for sending HTTP requests. Chai-http fulfills that role by communicating with the application/server, returning the responses, and using Chai assertions to verify the results.
In a Mocha test, we describe our tests using the describe
function, which typically contains our test cases. The individual test cases are implemented using the it
function, which is where we insert our assertions.
describe('API endpoint /colors', function() {
it('should return all colors', function() {
});
});
Create a new directory test
and inside that directory a file test.js
. Your directory structure should resemble the one below.
. ├── index.js ├── package.json └── test └── test.js
Write our Mocha test in test.js
:
'use strict';
const chai = require('chai');
const expect = require('chai').expect;
chai.use(require('chai-http'));
const app = require('../index.js'); // Our app
describe('API endpoint /colors', function() {
this.timeout(5000); // How long to wait for a response (ms)
before(function() {
});
after(function() {
});
// GET - List all colors
it('should return all colors', function() {
return chai.request(app)
.get('/colors')
.then(function(res) {
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.be.an('object');
expect(res.body.results).to.be.an('array');
});
});
// GET - Invalid path
it('should return Not Found', function() {
return chai.request(app)
.get('/INVALID_PATH')
.then(function(res) {
throw new Error('Path exists!');
})
.catch(function(err) {
expect(err).to.have.status(404);
});
});
// POST - Add new color
it('should add new color', function() {
return chai.request(app)
.post('/colors')
.send({
color: 'YELLOW'
})
.then(function(res) {
expect(res).to.have.status(201);
expect(res).to.be.json;
expect(res.body).to.be.an('object');
expect(res.body.results).to.be.an('array').that.includes(
'YELLOW');
});
});
// POST - Bad Request
it('should return Bad Request', function() {
return chai.request(app)
.post('/colors')
.type('form')
.send({
color: 'YELLOW'
})
.then(function(res) {
throw new Error('Invalid content type!');
})
.catch(function(err) {
expect(err).to.have.status(400);
});
});
});
Chai HTTP sends all requests to our express application instance, which we import in Line 8, and returns a promise (Lines 23, 35, 47, 63). Mocha waits for the promises inside the it
functions to resolve, before it continues to the next test case, unless it is setup to use callbacks. The before
and after
blocks can be used to setup our tests before any testing begins, and cleanup after all tests have been completed.
Running the Unit Tests
Before we run our unit tests, we should first install the application dependencies. Since we only use Mocha/Chai during development, it's a good practice to save them as devDependencies using the --save-dev
parameter.
$ npm install express body-parser --save
$ npm install mocha chai chai-http --save-dev
Your package.json
should be similar to the one below.
{
"name": "colors-api",
"version": "1.0.0",
"private": true,
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "mocha --recursive"
},
"dependencies": {
"body-parser": "^1.17.2",
"express": "^4.15.3"
},
"devDependencies": {
"chai": "^4.0.2",
"chai-http": "^3.0.0",
"mocha": "^3.4.2"
}
}
Note that, by default, mocha --recursive
runs all tests it finds under the test
directory. In addition, npm automatically inserts node_modules/.bin
into our shell's PATH, that's why we can execute Mocha without installing it globally.
To run our test, simply execute npm test
.
$ npm test
API endpoint /colors
✓ should return all colors
✓ should return Not Found
✓ should add new color
✓ should return Bad Request
4 passing (71ms)
If there are no errors, then the output should be similar to the above, otherwise Mocha will report the test case and code line where the test failed on.
You can find all source code in this tutorial in my GitHub repository.