Creating automated tests is something highly recommended. There are several types of tests: unitary, functional, integration and others. In this chapter, we will focus only on the integration tests. In our case we aim to test the outputs and behaviors of the API routes.
Creating an API with Express
First of all, we need to build a small and simple API which will be called task-api
and we will add some routes that will be enough to manage a task list. After that we will create some tests. For this purpose, let’s create a Node.js project by running these commands:
mkdir task - api
cd task - api
npm init
Let’s install some useful modules to build our API. We’ll use: express
as web framework, body-parser
as json serializer, lowdb
as json database and uuid
as unique id generator. To install them, just run this command:
npm install express body - parser lowdb uuid--save
And now with all dependencies installed, let’s build our API by creating the index.js
file in the project’s root:
// Loading modules
var express = require('express');
var lowdb = require('lowdb');
var storage = require('lowdb/file-sync');
var uuid = require('uuid');
var bodyParser = require('body-parser');
// Instantiating express module
var app = express();
// Instantiating database module
// This will create db.json storage in the root folder
app.db = lowdb('db.json', {
storage: storage
});
// Adding body-parser middleware to parser JSON data
app.use(bodyParser.json());
// CRUD routes to manage a task list
// Listing all tasks
app.get('/tasks', function(req, res) {
return res.json(app.db('tasks'));
});
// Finding a task
app.get('/tasks/:id', function(req, res) {
var id = req.params.id;
var task = app.db('tasks').find({
id: id
});
if (task) {
return res.json(task);
}
return res.status(404).end();
});
// Adding new task
app.post('/tasks', function(req, res) {
var task = req.body;
task.id = uuid();
app.db('tasks').push(task)
return res.status(201).end();
});
// Updating a task
app.put('/tasks/:id', function(req, res) {
var id = req.params.id;
var task = req.body;
app.db('tasks')
.chain()
.find({
id: id
})
.assign(task)
.value()
return res.status(201).end();
});
// Delete a task
app.delete('/tasks/:id', function(req, res) {
var id = req.params.id;
app.db('tasks').remove({
id: id
});
return res.status(201).end();
});
// API server listing port 3000
app.listen(3000, function() {
console.log('API up and running');
});
// Exporting the app module
module.exports = app;
Introduction to Mocha
To create and execute these tests, we will have to use a test runner. We’ll use Mocha that is very popular in Node.js community.
Mocha has the following features:
- TDD style;
- BDD style;
- Code coverage HTML report;
- Customized test reports;
- Asynchronous test support;
- Easily integrated with the modules:
should
,assert
andchai
.
And it is a complete environment to create tests. You can learn more about it by accessing: mochajs.org.
Setting up the test environment
In our project, we are going to explore integration tests to create some tests for our API routes. To create them, we are going to use these modules:
mocha
: the main test runner;chai
to write BDD tests viaexpect
function;supertest
to do some requests in the API’s routes;
So let’s install them:
npm install mocha chai supertest--save - dev
Now, let’s encapsulate the mocha
test runner into the npm test
alias command to internally run the command: NODE_ENV=test mocha test/**/*.js
which is responsible for running all tests. And to start the API server, we are going to use the npm start
alias command to run the node index.js
command. To implement these new commands, edit the package.json
and include the scripts.start
and scripts.test
attributes:
{
"name": "task-api",
"version": "1.0.0",
"description": "Task list API",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "NODE_ENV=test mocha test/**/*.js"
},
"dependencies": {
"body-parser": "^1.15.0",
"express": "^4.13.4",
"lowdb": "^0.12.5",
"uuid": "^2.0.2"
},
"devDependencies": {
"chai": "^3.5.0",
"mocha": "^2.4.5",
"supertest": "^1.2.0"
}
}
To finish our test’s environment setup, let’s prepare some Mocha settings. This helper will load the API server and the chai
, supertest
and uuid
modules as global variables. To implement this helper, create the file test/helpers.js
file:
var supertest = require('supertest');
var chai = require('chai');
var uuid = require('uuid');
var app = require('../index.js');
global.app = app;
global.uuid = uuid;
global.expect = chai.expect;
global.request = supertest(app);
Then, let’s create a simple file which allows to include some settings as parameters to the mocha
module. This will be responsible to load first the test/helpers.js
and also use the --reporter spec
flag to customize the test’s report. So, create the test/mocha.opts
file using the following parameters:
--require test / helpers
--reporter spec
Writing tests
We finished the setup related to the test environment. What about actually testing something? What about writing some tests for the API?
To create tests, first you need to use the describe
function to describe what this test is for, and inside it you create tests by using the it
function. You can also use nested describe
‘s. In all of the tests we are going to use the request
module to do some requests in the routes of our API. To validate the tests’ result we use the expect
module, which has a lot of useful functions to check if some variable is responding according to an expected behavior. Finally, to finish a test you must run in the the done
function. To learn all assertion’s functions from expect
module, you can take a look at chai BDD style documentation here and you can learn about everything from supertest module too. To see in practice how to write tests, let’s create the main test/routes/index.js
file and write these tests below:
describe('Task API Routes', function() {
// This function will run before every test to clear database
beforeEach(function(done) {
app.db.object = {};
app.db.object.tasks = [{
id: uuid(),
title: 'study',
done: false
}, {
id: uuid(),
title: 'work',
done: true
}];
app.db.write();
done();
});
// In this test it's expected a task list of two tasks
describe('GET /tasks', function() {
it('returns a list of tasks', function(done) {
request.get('/tasks')
.expect(200)
.end(function(err, res) {
expect(res.body).to.have.lengthOf(2);
done(err);
});
});
});
// Testing the save task expecting status 201 of success
describe('POST /tasks', function() {
it('saves a new task', function(done) {
request.post('/tasks')
.send({
title: 'run',
done: false
})
.expect(201)
.end(function(err, res) {
done(err);
});
});
});
// Here it'll be tested two behaviors when try to find a task by id
describe('GET /tasks/:id', function() {
// Testing how to find a task by id
it('returns a task by id', function(done) {
var task = app.db('tasks').first();
request.get('/tasks/' + task.id)
.expect(200)
.end(function(err, res) {
expect(res.body).to.eql(task);
done(err);
});
});
// Testing the status 404 for task not found
it('returns status 404 when id is not found', function(done) {
var task = {
id: 'fakeId'
}
request.get('/tasks/' + task.id)
.expect(404)
.end(function(err, res) {
done(err);
});
});
});
// Testing how to update a task expecting status 201 of success
describe('PUT /tasks/:id', function() {
it('updates a task', function(done) {
var task = app.db('tasks').first();
request.put('/tasks/' + task.id)
.send({
title: 'travel',
done: false
})
.expect(201)
.end(function(err, res) {
done(err);
});
});
});
// Testing how to delete a task expecting status 201 of success
describe('DELETE /tasks/:id', function() {
it('removes a task', function(done) {
var task = app.db('tasks').first();
request.put('/tasks/' + task.id)
.expect(201)
.end(function(err, res) {
done(err);
});
});
});
});
Now you just need to run all the tests. To do so, just run the command:
npm test
And see with your own eyes the successful result of these tests:
Conclusion
If you reached this point, then you have created a small API using Node.js with some integration tests to ensure the code quality of your project. This was all attained using some popular frameworks like: mocha
, chai
and supertest
.
Finally, don't forget to pay special attention if you're developing commercial JavaScript apps that contain sensitive logic. You can protect them against code theft, tampering, and reverse engineering by starting your free Jscrambler trial.