DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Driving DevOps With Smart, Scalable Testing
  • Unit Testing Large Codebases: Principles, Practices, and C++ Examples
  • Practical Use of Weak Symbols
  • Generate Unit Tests With AI Using Ollama and Spring Boot

Trending

  • DZone's Article Submission Guidelines
  • Enforcing Architecture With ArchUnit in Java
  • MCP Servers: The Technical Debt That Is Coming
  • The End of “Good Enough Agile”
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Unit Testing With JS Promise

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.

By 
Venkatraman Ramamoorthy user avatar
Venkatraman Ramamoorthy
·
Jan. 04, 18 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
17.8K Views

Join the DZone community and get the full member experience.

Join For Free

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 before, beforeEach, 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.

  • Spies
  • Stubs
  • Mocks
  • Fake timers
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!

unit test

Published at DZone with permission of Venkatraman Ramamoorthy. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Driving DevOps With Smart, Scalable Testing
  • Unit Testing Large Codebases: Principles, Practices, and C++ Examples
  • Practical Use of Weak Symbols
  • Generate Unit Tests With AI Using Ollama and Spring Boot

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!