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.
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
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.
In the index.js we make available the environment configuration to the app without any logic
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.
We need to install required dependencies by running:
npm install supertest mocha chai --save-dev
- Go to the root of your folder and create a folder where all the test will be and run:
mkdir ../test && cd $_
- Create a test file
touch todos.test.js
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');
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:
- 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’.
-
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
:
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:
However, if we run the test again, the first one is going to fail.
I'll leave you to figure it out before we clean the whole thing up.
- 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
As you can see, with these few changes to our initial app, life is made easier and development made speedy.
Love your work, Olatunde. I have problem running ‘npm run test’. Output tells me ‘NODE_ENV is not recognized as a command’ any suggestions? I have triple-checked for typo’s. Would be nice if you could publish the finished test-project on github.
Thanks! Yes, it is on github, https://github.com/generalgmt/RESTfulAPITutorial/tree/superTest I’ll try to reproduce the error though.
Thanks alot! Seems like my OS is the problem. Windows 10 must have
“scripts”: {
“test”: “set NODE_ENV=test && mocha --timeout 10000”,
“start”: “nodemon server.js”
}
But then test fails because the mogodb hasn’t changed to localhost/TododbTest and still running on port 3000.
when I am doing npm test run , getting the below failure :
open()
is deprecated in mongoose >= 4.11.0, useopenUri()
instead, or set theuseMongoClient
option if usingconnect()
orcreateConnection()
. See http://mongoosejs.com/docs/4.x/docs/connections.html#use-mongo-clienttodo list RESTful API server started on: 4000
events.js:141
throw er; // Unhandled ‘error’ event
^
MongoError: failed to connect to server [localhost:27017] on first connect [MongoError: connect ECONNREFUSED 127.0.0.1:27017]
Hi Did you find a resolution to this issue?
Content is lit. The tech space needs more Olatunde Garuba
HI