12/08/2018, 13:13

Test your Nodejs code using Mochajs

Mocha - simple, flexible, fun A simple introduction on Mochajs homepage Mocha is a feature-rich JavaScript test framework running on Node.js and the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping ...

Mocha - simple, flexible, fun

A simple introduction on Mochajs homepage

Mocha is a feature-rich JavaScript test framework running on Node.js and the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases.

Why I learned Mocha

Recently, I have been working on a project using AWS Lambda and I used Nodejs for writing Lambda functions. It is quite an experience that I have been working on a totally new area and this project is really a challenge to me.

It is a requirement of the project that I need to provide test for each Lambda function so here I am, digging into Mocha documentation.

Why Mocha? It's an equivalent framework to RSpec in Ruby, and everywhere on the Internet is suggesting me to use it.

ES6 or ES5 ?

Lambda function, for now, supports Nodejs v0.10.36. This means that Lambda function only executes ES5 code. But, but ES6 is better... right?

So I wrote Lambda functions in ES6 and used a transpiler - Babel to convert my ES6 code to ES5. It means my PRs are written in ES6 while my deployment packages contain ES5 code.

Should/Expect or simple Assertion?

There are some assertion libraries suggested on Mochajs Wiki page

And they provide you the similar API like what RSpec has provided you:

it { expect(subject).to include(100) }
it('should contain 100', () => {
  expect(subject).to.contain(100);
});

They look similar but not quite, if you take a closer look you will notice that in Mocha you have to specify a specific and accurate message to describe what you are testing, and at the same time you need to know the right API to use in your test. Not like RSpec, which they encourage you to write example without giving a description.

Then when the test failed, you might see duplicated messages saying your subject should contain 100 - one from your example description and one from the assertion library.

An excellent engineer has suggested me to use power-assert

The library is written based on a simple philosophy:

Power Assert in JavaScript. Provides descriptive assertion messages through standard assert interface. No API is the best API.

Most of the time you will have to remember a single syntax assert(expression). The above test can be rewritten as following:

import assert from 'power-assert';

describe('Some test', () => {
  let subject = [199, 1, 1000];

  it('should contain 100', () => {
    assert(subject.indexOf(100) === 0);
  });
});

And it will display AssertionError: false == true, but we already know what we expected from this test, which is should contain 100

mocha --compilers js:babel/register test.js

  Subject
    1) should contain 100

  0 passing (21ms)
  1 failing

  1) Subject should contain 100:

      AssertionError: false == true
      + expected - actual

      -false
      +true

I hope you get the idea - we don't need to remember any complicated API, we can use an API as simple as assert.

Note: Sometimes you might find that Babel is not converting correctly, then you might need to install v5.8.29

Testing the Asynchronous

In Nodejs, you find yourself writing a piece of code which is executed asynchronously. It is ok, you can test that using Mocha.

If you're using a callback, passing done callback will help you do the trick. Or if you return a promise, you can return a promise in example to inform Mocha that it needs to wait for this example to run asynchronously. For example:

class Person {
  constructor({ name, address, dob }) {
    this.name = name;
    this.address = address;
    this.dob = dob;
    this.stamina = 100;
  }

  walkWithCallback(args, callback) {
    // drain stamina
    // do something with callback
  }

  walkWithPromise(args) {
    // drain stamina
    // return a Promise
  }
}

The test with done callback

describe('Person', () => {
  const name = 'Peter';
  const address = '123 Right street';
  const dob = 'November 5th 1970';
  let person;

  // similar to before - do in RSpec
  beforeEach(() => {
    person = new Person({ name, address, dob })
  });

  describe('#walkWithCallback()', () => {
    it('should drain some stamina', (done) => {
      person.walkWithCallBack('some args', (err, data) => {
        assert(person.stamina < 100);
        done();
      });
    });
  });

  describe('#walkWithPromise()', () => {
    it('should drain some stamina', () => {
      return person.walkWithPromise('some args').then(() => {
        assert(person.stamina < 100);
      });
    });
  });
});

I prefer to return a Promise in an asynchronous because it's easier to test and to chain method as well.

Promisify your functions

In the previous section, I have shown how to test Person#walkWithCallback(). There is a way to turn that method into a method that return Promise by using es6-promisify

Then you can promisify any function using callback, the test can be rewritten as following:

import promisify from 'es6-promisify';

Person.prototype.walkWithCallbackAsync = promisify(Person.prototype.walkWithCallback);

describe('Person', () => {
  const name = 'Peter';
  const address = '123 Right street';
  const dob = 'November 5th 1970';
  let person;

  // similar to before - do in RSpec
  beforeEach(() => {
    person = new Person({ name, address, dob })
  });

  describe('#walkWithCallback()', () => {
    it('should drain some stamina', () => {
      return person.walkWithCallBackAsync('some args').then(() => {
        assert(person.stamina < 100);
      });
    });
  });
});

Pretty awesome, right! Anyway I prefer to return a Promise in an asynchronous function!!!

Good sources to learn Mocha

https://onsen.io/blog/mocha-chaijs-unit-test-coverage-es6/ https://mochajs.org/ http://tobyho.com/2015/12/16/mocha-with-promises/

0