All You Need to Know About Integration Testing: SuperTest, Mocha, and Chai

Published Apr 14, 2017
All You Need to Know About Integration Testing: SuperTest, Mocha, and Chai

What is Testing?

Testing is the process of evaluating a system or its component(s) with the intent to find whether it satisfies the specified requirements or not.

Testing is executing a system in order to identify any gaps, errors, or missing requirements in contrary to the actual requirements.

This tutorial will give you a basic understanding on software testing, its types, and other related terminologies.

Types of Testing

There are several types of software engineering test such as (Unit Testing, Integration Testing, Functional Testing, System Testing, Stress Testing, Performance Testing, Usability Testing, Acceptance Testing). However, I’ll be talking on Integration testing in this article.

Integration testing is logically an extension of unit testing. When two or more units are combined, they result into what is called interface. This interfaces are further combined to form components. Integration testing identifies problems that occur at the interface level. This method reduces the number of potential problems.

Integration testing helps to ensure that the functional, performance, and reliability between the units that are integrated are working properly.

Why Should I Do Integration Testing?

  • Integration testing helps to catch bugs at the earlier stage in the software development cycle.
  • Integration testing is easy to integrate with daily builds
  • Integration testing is easy to test in local environment
  • Tests run faster compared to end to end tests.
  • Integration testing are more reliable and used in isolating failures.
  • Integration tests are used detecting system-level issues.

Who Should Test?

Apart from the fact that testing is a good yardstick for fast and good software development, which helps you find code breaks with few modifications/changes. I get frustrated having to go over to the postman each time to check my APIs. If you're like me, and want to have almost everything done at a go, or you yearn for fast and good software application, you need to embrace software test driving development.

Prerequisite & Tools

Node.js running endpoints are the prereqs and here are some tools you can use:

SuperTest:

SuperTest offers a very simple way to test APIs with just few lines of commands and well documented doc.

Mocha:

Mocha is a JavaScript test framework that runs in the browser and on Node.js. One of the major features of Mocha is its ability to make asynchronous testing simple and fun, allowing for flexible and accurate reporting.

Chai:

Chai shines on the freedom of choosing the interface we prefer: “should”, “expect”, “assert” they are all available. I personally use should but you are free to check out the API and switch to the others two. Lastly Chai HTTP addon allows Chai library to easily use assertions on HTTP requests which suits our needs.

Note: Chai has several interfaces that allow the developer to choose the most comfortable. The chain-capable BDD styles provide an expressive language & readable style, while the TDD assert style provides a more classical feel.
Our test case will be to write an integration test for a simple Nodejs server APIs from one of my previous tutorials Nodejs in ten minutes.

Now, if you have a nodejs app already, you can jump to step 3. However, you need to make sure your express server module is exported. Use the steps as applicable.

Few changes will be made to the app for our test case:

Step 1: Make the server.js exportable.

Step 2: Add environment configurations for various environments.

Step 3: Write basic integration tests for the APIs.

Step 1: Make the server.js exportable.

Add module.exports = app; to the server.js.

The module.exports is an object created by the Module system — it helps to provide some global-looking variables. Whatever you assign to module.exports or exports, it will be exposed as a module.

Screen Shot 2017-04-09 at 1.33.58 AM.png

Step 2: Add environment configurations for various environments.

Create a folder in the root of the app called config mkdir config.
Inside this folder, we will create 4 files called production.js, test.js, development.js and index.js.

cd config

and then run

touch production.js test.js index.js development.js

Screen Shot 2017-04-06 at 5.35.46 PM.png

The main aim here is to ensure that our configurations are not tampered with, such as the database.

In the development.js we will export our configuration to make it available elsewhere when needed.

Paste the following in it and save.

'use strict';

module.exports = {
  env: 'development',
  db: 'mongodb://localhost/Tododb',
  port: process.env.PORT || 4000,
};

This is repeated for test and production.

Screen Shot 2017-04-08 at 8.07.53 PM.png

Screen Shot 2017-04-08 at 8.07.45 PM.png
In the index.js we make available the environment configuration to the app without any logic 😃

Screen Shot 2017-04-08 at 8.07.34 PM.png

Step 3: Write basic integration tests for the APIs

Before we can write our basic integration test for the todolist APIs routes, as shown in the diagram below, using SuperTest, Mocha, and Chai.

Screen Shot 2017-04-08 at 8.16.21 PM.png

We need to install required dependencies by running:

npm install supertest mocha chai --save-dev
  1. Go to the root of your folder and create a folder where all the test will be and run:
mkdir ../test && cd $_
  1. Create a test file touch todos.test.js

Screen Shot 2017-04-06 at 11.13.11 PM.png
3. In the todos.test.js file, we need to make some files available, such as server, Chai (for the BDD), and SuperTest by:

'use strict';

var app = require('../server'),
  chai = require('chai'),
  request = require('supertest');

Screen Shot 2017-04-08 at 8.21.20 PM.png
Save the chai api(expect) that we will be using, to enforce DRY
and then write the tests.

Generally, tests are grouped/nested in an identifier/suite called describe while the test cases are actually tested in the it identifier/suite. This, as a rule of thumb, follows a design patter for BDD. You can read more about this here

Hence, in our case todoList APIs, we will have something like this:

  1. Run the following code:
describe('Todos list API Integration Tests', function() {

});

As you know, we have 5 different endpoints, but will start with the ‘GET’ on ‘/tasks’.

  1. To do this, we write another test suit described in the first suite. This will group all the test that has to do with the GET /tasks

    (Note: as the overall suite, every other group will be nested within this.)

describe('Todos list API Integration Tests', function() {
  describe('#GET / tasks', function() { 
    it('should get all tasks', function(done) { 
      request(app) .get('/tasks')
        .end(function(err, res) { 
          expect(res.statusCode).to.equal(200); 
          expect(res.body).to.be.an('array'); 
          expect(res.body).to.be.empty; 
          done(); 
        }); 
    });
  });
});

Here, we are saying, make a get request to the /tasks route in our app. Once done, it is expected that the response status code should be 200 (i.e okay). This is will be true if we have a working route /tasks with a get request.

Also, we are saying the response body type should be an array and should be empty. This is also true because we have not created any task in the test database.

After this, let's update our package.json file to include the test command in the script object by adding

"test": "NODE_ENV=test mocha — timeout 10000"

On the terminal, run npm run test:

Screen Shot 2017-04-08 at 9.23.57 PM.png
3. Create a task test to create a test suite.

Just like what we did in the first test, we’ll declare the group suite and then the test case with the corresponding description and test name, post on the route, and send the data we want to create.

However, we have created a global variable to store the task created/to create

'use strict';

var app = require('../server'),
  chai = require('chai'),
  request = require('supertest');

var expect = chai.expect;
describe('API Tests', function() { 
  var task = { 
    name: 'integration test' 
  };
  describe('# Get all tasks', function() { 
    it('should get all tasks', function(done) { 
      request(app) .get('/tasks') .end(function(err, res) { 
        expect(res.statusCode).to.equal(200); 
        expect(res.body).to.be.an('array'); 
        expect(res.body).to.be.empty; 
        done(); 
      }); 
    }); 
  }); 

  describe('## Create task ', function() { 
    it('should create a task', function(done) { 
      request(app) .post('/tasks') .send(task) .end(function(err, res) { 
        expect(res.statusCode).to.equal(200); 
        expect(res.body.name).to.equal('integration test'); 
        task = res.body; 
        done(); 
      }); 
    }); 
  }); 
});

As you can see, on running npm run test, this passes now:

Screen Shot 2017-04-08 at 9.24.11 PM.png

However, if we run the test again, the first one is going to fail.

Screen Shot 2017-04-08 at 9.29.09 PM.png

I'll leave you to figure it out before we clean the whole thing up.

  1. Create a test suite to get a single task
describe('Get a task by id', function() { 
  it('should get a task', function(done) { 
    request(app) .get('/tasks/' + task._id) .end(function(err, res) { 
      expect(res.statusCode).to.equal(200); 
      expect(res.body.name).to.equal('integration test'); 
      done(); 
    }); 
  }); 
});

Almost the same as how we did it for all other routes.

You can find the complete code on Github — branch out to superTest please.

Some Icing on the Cake

Having gone this far, it would an awesome thing if we could see how our tests is doing with respect to our app in terms of test coverage 😃

Let’s add code coverage to the app!

Istanbul, is a JavaScript code coverage tool that computes statement, line, function, and branch coverage with module loader hooks to transparently add coverage when running tests. It supports all JS coverage use cases, including unit tests, server side functional tests, and browser tests. Even better? It was built for scale.

Fortunately, we don’t have to do anything to install Istanbul. We can just do this:

npm install istanbul --save-dev

and then modify our package.json file.

In the package.json file, add test with coverage to the script object

"test-coverage": "NODE_ENV=test istanbul cover _mocha —- -R spec"

Then, run

npm run test-coverage

Screen Shot 2017-04-09 at 8.28.13 PM.png
As you can see, with these few changes to our initial app, life is made easier and development made speedy.

Discover and read more posts from Olatunde Garuba
get started
Enjoy this post?

Leave a like and comment for Olatunde

16
7