Testing functions with Mocha and Chai

The first step to creating a continuously deployable Lambda function is to write unit tests. One of the easiest and simplest ways to do so is by using a combination of two frameworks called Mocha and Chai.

First, a little bit about the two frameworks without going into too much information:

        describe('myLambda',function(){
          //assertions about your code go here
        }

For the purpose of this chapter, we will only concentrate on the expect interface. The expect interface uses simple chainable language to construct assertions, for example: expect([1,2,3]).to.equal([1,2,3]). Combining the expect assertion with the describe statement from Mocha looks something similar to the code snippet shown as follows:

describe('myLambda',function(){
   it('Check value returned from myLambda',function(){
   expect(retValue).to.equal(‘someValue');
}

Mocha has an assertion module of it's own, however I find Chai to be a little bit more flexible and extensible.

It is fairly simple and straightforward to setup Mocha and Chai on your development server. First up, create two folders; one to house the code that we are using and one to contain the test code:

# mkdir code
# mkdir test

Once this is done, install the Mocha framework and the Chai expectation library using the following commands:

# npm install mocha
# npm install chai

You can optionally add the --global parameter to save the npm modules globally over your dev system.

With the necessary packages downloaded, we move on to the code part. Copy and paste the calculator.js file in the recently created code directory. Next, copy and paste the following code into the test directory:

# vi test/calculatorTest.js
var expect = require('chai').expect;
var myLambda = require('../lib/calculator');
var retError, retValue ;

describe('myLambda',function(){
  context('Positive Test Case', function(){
    before('Calling myLambda function', function(done){
      var event = {
        num1: 3,
        num2: 2,
        operand: "+"
      };
      var context= {
        functionName: "calculator"
      };
      myLambda.handler(event, context, function (err, value) {
        retError = err ;
        retValue = value ;
        done();
      });
    });
    it('Check that error is not returned from myLambda',function(){
      expect(retError).to.be.a('null');
    });
    it('Check value returned from myLambda',function(){
      expect(retValue).to.equal(5);
    });
  });
  context('Negative Test Case - Invalid Numbers', function(){
    before('Calling myLambda function', function(done){
      var event = {
        num1: "num",
        num2: 2,
        operand: "div"
      };
      var context= {
        functionName: "calculator"
      };
      myLambda.handler(event, context, function (err, value) {
        retError = err ;
        retValue = value ;
        done();
      });
    });
    it('Check that error is returned from myLambda',function(){
      //var retErrorString = retError.toString();
      expect(retError).to.equal("Invalid Numbers!");
    });
    it('Check value returned from myLambda is undefined',function(){
      expect(retValue).to.be.an('undefined');
    });
  });
  context('Negative Test Case - Zero Divisor', function(){
    before('Calling myLambda function', function(done){
      var event = {
        num1: 2,
        num2: 0,
        operand: "div"
      };
      var context= {
        functionName: "calculator"
      };
      myLambda.handler(event, context, function (err, value) {
        retError = err ;
        retValue = value ;
        done();
      });
    });
    it('Check that error is returned from myLambda',function(){
      //var retErrorString = retError.toString();
      expect(retError).to.equal("The divisor cannot be 0");
    });
    it('Check value returned from myLambda is undefined',function(){
      expect(retValue).to.be.an('undefined');
    });
  });
  context('Negative Test Case - Invalid Operand', function(){
    before('Calling myLambda function', function(done){
      var event = {
        num1: 2,
        num2: 0,
        operand: "="
      };
      var context= {
        functionName: "calculator"
      };
      myLambda.handler(event, context, function (err, value) {
        retError = err ;
        retValue = value ;
        done();
      });
    });
    it('Check that error is returned from myLambda',function(){
      //var retErrorString = retError.toString();
      expect(retError).to.equal("Invalid Operand");
    });
    it('Check value returned from myLambda is undefined',function(){
      expect(retValue).to.be.an('undefined');
    });
  });
});

Your final directory structure should resemble something like what is shown in the following screenshot:

With the setup all done, running your unit test on the calculator code is as easy as typing mocha!! Go ahead and execute the mocha command from your work directory. If all goes well, you should see a bunch of messages displayed on the screen, each taking about a particular test that was run against the calculator.js code. But where did all this come from? Let us look at it one step at a time.

First up, we wrote a bunch of unit test cases in the calculatorTest.js code using both Mocha and Chai.

The first couple of lines are to include the npm modules like Chai and the calculator.js code:

var expect = require('chai').expect;
var myLambda = require('../code/calculator');
var retError, retValue ;

Now, we will use the describe function of Mocha to describe the myLambda function. The describe function takes a simple string as its first argument and the second argument is a function while will represent the body of our expectations from the myLambda function:

describe('myLambda',function(){

Here we test the positive test case first. A positive test case is nothing more than providing the code with valid data to run.

Since myLambda is an asynchronous function and we need it to execute first before we can verify expect values, we use the done parameter with the before hook.

We define the event and context parameters and make a call to the myLambda function and calling done in its callback to let Mocha know that it can now move on to the expectations:

context('Positive Test Case', function(){
  before('Calling myLambda function', function(done){
    var event = {
      num1: 3,
      num2: 2,
      operand: "+"
    };
    var context= {
      functionName: "calculator"
    };
    myLambda.handler(event, context, function (err, value) {
      retError = err ;
      retValue = value ;
      done();
    });
  });

The output of this positive test block of unit test looks something like what is shown in the following screenshot:

Here, we use the expect interface from the chai module and check our error and result values respectively:

  it('Check that error is not returned from myLambda',function(){
    expect(retError).to.be.a('null');
  });
  it('Check value returned from myLambda',function(){
    expect(retValue).to.equal(5);
  });
});

Similarly, you can now add more assertions for positive and negative test cases as shown previously, changing the parameters with each test to ensure the calculator.js code works as expected.

Here is an example of a simple negative test case for our calculator.js code as well, we check how the code behaves if the values passed to it are non-numeric in nature (invalid data input):

context('Negative Test Case - Invalid Numbers', function(){
  before('Calling myLambda function', function(done){
    var event = {
      num1: "num",
      num2: 2,
      operand: "div"
    };
    var context= {
      functionName: "calculator"
    };
    myLambda.handler(event, context, function (err, value) {
      retError = err ;
      retValue = value ;
      done();
    });
  });
  it('Check that error is returned from myLambda',function(){
    //var retErrorString = retError.toString();
    expect(retError).to.equal("Invalid Numbers!");
  });
  it('Check value returned from myLambda is undefined',function(){
    expect(retValue).to.be.an('undefined');
  });
});

The following screenshot shows the output for the preceding code:

On similar lines, you can write and expand your own unit test cases to check and verify if the code is working as expected. Just do a few changes in the events and check the outcome!