Node.js - Unit Testing with Mocha, Proxyquire, Sinon, Chai

Unit testing is a software testing method in which the smallest testable unit is tested independenly by running some test cases. There are some advantages of unit testing. For example, you can find problems earlier in the development lifecycle. It also ensures the module still works everytime the code changes. In addition, it may also help other developers understanding how the module works by reading the test cases.

In order to create unit tests, programmers usually need testing libraries to reduce efforts. In this tutorial, I'll show you how to perform unit testing in Node.js with the help of some libraries which include mocha, proxyquire, sinon and chai.

Mocha

Mocha is a very popular JavaScriptt testing framework for Node.js and browser. It's responsible to run tests serially and report how many test cases are passed and failed

Proxyquire

Proxyquire is used to proy Node.js's require by overriding dependencies. I'm sure you often use require in your code. In testing, the require doesn't always necessarily called because it may cause complexity in setting up the unit test. Therefore you need to override the require call by making a stub. Proxyquire makes it easy for you to do it.

Sinon

Sinon is used to spies, stubs and mocks in JavaScript. It works with any unit testing framework. Spies is used to get information about function calls, such as how many times a function was called and what argument were passed to the function. Stub is useful when you test a function that calls other function(s). To focus testing only on the function under test, you can stub the other functions by setting up a fixed return value. Therefore, it allows you to easily control the test to a specific path in the code. Mock is used for the function under test, to control how your unit is being used.

Chai

Chai is a JavaScript asssertion library that works with any JavaScript testing frameworks. It has some human-readable asserting styles and you're free to choose which one is the most comfortable for you. You can read the documentation of chai on their website.

Code Example

In this example, we're going to use a simple function, then we create the unit test for it. Previously, add the following dependencies in the devDependencies of package.json

package.json

  "chai": "~4.1.1",
  "mocha": "~5.2.0",
  "proxyquire": "~2.0.1",
  "sinon": "~6.0.0",

Here is the function we're going to test

helpers/post.js

  const postQueries = require('../queries/post');

  exports.getPostDetails = (criteria) => {
    return postQueries.getPostByCriteria(criteria) // query to db
      .then((post) => {
        if (!post) {
          return Bluebird.reject(new Error());
        }

        post.title += ' - Woolha';
        return post;
      })
      .catch(() => {
        return null;
      });
  };

The function above is very simple - it's used to get a post from database and format it by appending " - Woolha" on its title property. If there is no post found in the database, it will return null.

As we only test getPostDetails, we should stub getPostByCriteria. There are two test cases: when the post is not found in database (getPostByCriteria returns null) and when the post found in database (getPostByCriteria returns the post object). Cause our goal is testing getPostDetails, we have to stub getPostByCriteria. We set it to return null for the first case and a non-empty object for the second case. That's allow us to easily control the flow so that the test cases cover all possible paths.

Here is the structure of our test script

tests/helpers/post.spec.js

  describe('getPostDetails', () => {
    context('when post not found', () => {
      let result;

      it('should return null', () => {
        
      });
    });

    context('when post found', () => {
      let result;

      it('should return post whose title appended with site name', () => {
        
      });
    });
  });

In the helpers/post.js, there is a dependency to ../queries/post using require. We have to override the dependency using proxyquire

tests/helpers/post.spec.js

  const postQueriesStub = {
    getPostByCriteria: _.constant({}),
    '@noCallThru': true,
  };

  const postHelpers = proxyquire('../../helpers/post.js', {
    '../queries/post': postQueriesStub,
    '@noCallThru': true,
  });

Then, we stub the getPostByCriteria function for each of the two cases using proxyquire. After that, the stubbed function should be restored. Here is for the first case.

tests/helpers/post.spec.js

  before('stub query methods', () => {
    sinon
      .stub(postQueriesStub, 'getPostByCriteria')
      .returns(Bluebird.resolve(null));
  });

  after('restore stub', () => {
    postQueriesStub.getPostByCriteria.restore();
  });

And here is for the second case.

tests/helpers/post.spec.js

  before('stub query methods', () => {
    sinon
      .stub(postQueriesStub, 'getPostByCriteria')
      .returns(Bluebird.resolve({
        title: 'Title',
        content: 'Example',
      }));
  });

  after('restore stub', () => {
    postQueriesStub.getPostByCriteria.restore();
  });

Next, we call the function by adding the folllowing.

tests/helpers/post.spec.js

  before('call the function', () => {
  return postHelpers.fetchAndFormatPostDetails()
    .then((_result) => {
      result = _result;
    });
  });

Like unit testing with other frameworks, we need to add expectation for each cases. We should add expectation inside it block for each test cases. Here is for the first case

tests/helpers/post.spec.js

   it('should return null', () => {
    expect(result).to.equals(null);
  });

And for the second case.

tests/helpers/post.spec.js

   it('should return post whose title appended with site name', () => {
    expect(result.title).to.equals('Title - Woolha');
  });

Below is the full code

tests/helpers/post.spec.js

  const _ = require('lodash');
  const Bluebird = require('bluebird');
  const chai = require('chai');
  const expect = chai.expect;
  const proxyquire = require('proxyquire');
  const sinon = require('sinon');

  const postQueriesStub = {
    getPostByCriteria: _.constant({}),
    '@noCallThru': true,
  };

  const postHelpers = proxyquire('../../helpers/post.js', {
    '../queries/post': postQueriesStub,
    '@noCallThru': true,
  });

  describe('getPostDetails', () => {
    context('when post not found', () => {
      let result;

      before('stub query methods', () => {
        sinon
          .stub(postQueriesStub, 'getPostByCriteria')
          .returns(Bluebird.resolve(null));
      });

      before('call the function', () => {
        return postHelpers.fetchAndFormatPostDetails()
          .then((_result) => {
            result = _result;
          });
      });

      after('restore stub', () => {
        postQueriesStub.getPostByCriteria.restore();
      });

      it('should return null', () => {
        expect(result).to.be.equals(null);
      });
    });

    context('when post found', () => {
      let result;

      before('stub query methods', () => {
        sinon
          .stub(postQueriesStub, 'getPostByCriteria')
          .returns(Bluebird.resolve({
            title: 'Title',
            content: 'Example',
          }));
      });

      before('call the function', () => {
        return postHelpers.fetchAndFormatPostDetails()
          .then((_result) => {
            result = _result;
          });
      });

      after('restore stub', () => {
        postQueriesStub.getPostByCriteria.restore();
      });

      it('should return post whose title appended with site name', () => {
expect(result.title).to.equals('Title - Woolha'); }); }); });

You can run the test using node tests/helpers/post.spec.js. You'll get the result how many test cases are passed and failed.

That's the basic of creating a unit test in Node.js using mocha, proxyquire, sinon and chai.