Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Unit Testing With JS Promise

DZone's Guide to

Unit Testing With JS Promise

Learn about executing unit testing for JavaScript with JS Promise, which can help you work with asynchronous operations more smoothly.

· DevOps Zone ·
Free Resource

Read why times series is the fastest growing database category.

What Is Promise?

Promise is an Object introduced in ECMA Script 2015 as part of the 6th Edition. It represents the future completion of success/failure operations. It mainly helps to work smoothly with asynchronous operations. The Promise API mainly follows the Promises/A+ Specification.

Why We Need Promise

For instance, you have submitted the email form/search form and are waiting for the response. In that case, you will not be sure when the server will respond and it's a bit hard to predict the time and get the actual message after that particular execution time for the response. Promise can help fix this issue.

Simple Promise

Take a look at the below snippet, which uses the Promise API to resolve the success message after two seconds. You can increase the duration as well, but the console triggers whenever it gets the message. In this example, we used the setTimeout function to return after two seconds, like a server response.

(function simple () {
    let serviceResponse = new Promise((resolve, reject) => {
        setTimeout(() => resolve('Success'), 2000);
    });

    serviceResponse.then((msg) => {
        console.log(`Hello Message is ${msg}`);
    }, () => {
        console.log('oops! its rejected');
    });
}());

Tools

MochaJS

Mocha is a JavaScript test framework running on Node.JS, featuring browser support, asynchronous testing, test coverage reports, and use of any assertion library. It can be used with libraries like should.js, expect.js, and chai.js.

ChaiJS

Chai is a BDD/TDD assertion library for Node and the browser that can be delightfully paired with any JavaScript testing framework.

SinonJS

SinonJS is a standalone library for test spies, stubs, and mocks for JavaScript, which works with any unit testing framework.

Installation

Use one of the commands below to install Mocha, Chai, and Sinon Module. These must be installed as Dev Dependencies modules, because it’s not required when the application running in the server, but during the build.

$ npm i -D mocha chai sinon

$ npm install mocha chai sinon --save-dev

Code Coverage

If you are very much interested in how much code is covered during unit testing, try Istanbul, which makes test coverage simple with various report formats.

$ npm i -D nyc

Simple Unit Testing

Let's have a look at the below code. It has a very simple function called basic and returns the promise data after 1.6 seconds (assume that it's retrieved from the database after 1.6 seconds).

function basic (result) {
    if (!result) {
        return Promise.reject('Failure');
    }

    return new Promise((resolve, reject) => {
        // Assume that this below data is retrieved from Database which takes 1.6 seconds.
        setTimeout(() => resolve(result), 1600);
    });
}

module.exports = {
    basic,
};

Unit Testing Basics

As explained in the previous step, we are adding Mocha and Chai for this basic Promise code. In order to write basic unit testing in Mocha, understanding the below two methods is a must.

  •  describe  - This function helps to groups selected test cases, so its easy to refer to and maintain.

  •  it  - This function helps to write your use case and test your library/module and expect the result.

Mocha Hooks

There are a few more functions which are considered Hooks. Below are the four important hooks that can be placed inside the describe function before any it function.

  •  before - runs before all tests in this block.

  •  beforeEach - runs before each test in this block.

  •  afterEach - runs after each test in this block.

  •  after - runs after all tests in this block.

Unit Testing Example

In this unit testing example, we have the main group- Basic Testing- and three subgroups- Basic, Error Case, and Success Case; each subgroup has one test case. Scroll through the code below to get a basic understanding.

const mocha = require('mocha');
const expect = require('chai').expect;
const basicFile = require('./basic');

describe('Basic Testing', () => {
    const noop = () => {};

    describe('Basic', () => {
        it('should have method `basic`', () => {
            expect(basicFile).to.be.an('object');
            expect(typeof basicFile.basic).to.equal('function');
        });

        // You can add more basic Test Cases.
    });

    describe('Error Case', () => {
        it('should return Error message for empty param', () => {
            return basicFile.basic().then(noop, (errorMessage) => {
                expect('Failure').to.equal(errorMessage);
            });
        });

        // You can add more Error Cases.
    });

    describe('Success Case', () => {
        it('should return param with Hello for valid param', () => {
            return basicFile.basic('Success').then((successMessage) => {
                expect('Success').to.equal(successMessage);
            }, noop);
        });

        // You can add more Success Cases.
    });

});

If it is still not clear, then have a look at the below explanation of each test case.

  • Basic - We are testing whether basic is a function.

  • Error Case - basic should return "Failure" when there is no parameter passed to the function.

  • Success Case - basic should return a given parameter as a result of a success scenario.

If you noticed the Success Case and Error Case scenario, we have used a return keyword for the Promise method so that it can wait and complete the operation. It's suggested to use return instead of done() for this type of test case.

Let's see a different example that includes an external file, the database library.

Unit Testing With Dependent Files

In real time, long files used to be split into a number of small files as models, libs, and services. Here, we will see a service file, which is included in the db helper file, where we get the db data.

const db = require('./db');

function getResultData(name) {
    let payload;

    if (name) {
        payload = {
            prop1: `value1-${name}`,
            prop2: `value2-${name}`,
        };
    }

    return new Promise((resolve, reject) => {
        db.getData(payload).then((data) => {
            if (data.error) {
                reject(data);
            }

            resolve(data);
        });
    });
}

module.exports = {
    getResultData,
};

DB Helper File - this is the file used above:

function getData(payload) {
    return new Promise((resolve, reject) => {
        if(!payload) {
            reject({
                error: true,
            });
        }

        setTimeout(() => {
            resolve({
                name1: 'value1',
                name2: 'value2',
            });
        }, 5000);
    });
}

module.exports = {
    getData,
};

This unit test included Mocha, Chai, and Sinon libraries, and files like Service and DB Helper.

Here, you will notice that beforebeforeEach, and after hooks were added for method request params and response with Sinon Stubs. It actually helps to proxy the calls of the DB library.

Why We Need Sinon

SinonJS has many good features. Below are a few methods we used for Stubs.

const mocha = require('mocha');
const expect = require('chai').expect;
const sinon = require('sinon');
const db = require('./db');
const service = require('./service');

describe('service.js', () => {
    let noop, label, payload,  response, dbStub;

    before(() => {
        noop = () => {};
        label = 'emp';
        payload;
        response;
        dbStub;
    });

    beforeEach(() => {
        payload = {
            prop1: `value1-${label}`,
            prop2: `value2-${label}`,
        };

        response = {
            name1: 'value1',
            name2: 'value2',
        };

        dbStub = sinon.stub(db, 'getData').callsFake(() => {
            return Promise.resolve(response);
        });
    });

    afterEach(() => {
        dbStub.restore();
    });

    it('should return error response', () => {
        return service.getResultData().then(noop, (data) => {
            expect(data).to.deep.equal({
                error: true,
            });
        });
    });

    it('should return success response', () => {
        return service.getResultData(label).then((data) => {
            expect(data).to.deep.equal(response);
        });
    });
});

Unit Testing With Async.js

Async is a utility module which provides straightforward, powerful functions for working with asynchronous JavaScript. Although originally designed for use with Node.js and installable via npm install --save async, it can also be used directly in the browser.

In this below example, We use async.parallel where we need to trigger the DB Service Method at the same time.

const async = require('async');
const db = require('./db');

function myAsyncParallel (callback) {
    async.parallel({
        first: db.userData.bind(null, 'first arg'),
        second: db.activityData.bind(null, 'second arg')
    }, callback);
}

module.exports = {
    myAsyncParallel,
};

Similar to the previous example, we added Mocha, Chai, and Sinon and added only one example for a success scenario.

We are mainly bypassing the DB Methods db.userData and db.activityData to return the custom data using Sinon Stub.

In this test case, we are checking that the expected result is equal to the method response.

const mocha = require('mocha');
const expect = require('chai').expect;
const sinon = require('sinon');
const db = require('./db');
const asyncFile = require('./parallel');

describe('Async Parallel.js', () => {
    let params, resultValues, userDataStub, activityDataStub, activityDataSpy;

    before(() => {
        params = ['first arg', 'second arg'];
        resultValues = ['First Result', 'Second Result'];
    });

    beforeEach(() => {
        userDataStub = sinon.stub(db, 'userData').callsFake((arg, callback) => {
            callback(null, {
                arg,
                result: resultValues[0]
            });
        });

        activityDataStub = sinon.stub(db, 'activityData').callsFake((arg, callback) => {
            callback(null, {
                arg,
                result: resultValues[1]
            });
        });        
    });

    it('should return the response', () => {
        return asyncFile.myAsyncParallel(function(error, results) {
            expect(results).to.deep.equal({
                first: {
                    arg: params[0],
                    result: resultValues[0]
                },
                second: {
                    arg: params[1],
                    result: resultValues[1]
                }
            });
        });
    });
});

Stay tuned for the next article. Happy coding!

Learn how to get 20x more performance than Elastic by moving to a Time Series database.

Topics:
promise ,unit testing ,devops ,javascript

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}