JS Unit tests with

mocha, sinon & chai

Plan

  • Setup
  • Mocha test suite
  • Chai syntax
  • CODING: Basic case
  • Sinon spies and stubs
  • CODING: Case with function stubbing
  • Asynchronous tests
  • CODING: Case with promises

Setup

Stack

All are packages to add in the package.json

Layout

  • Options
  • Code installed once
  • First source file to test and its test file
src
└── service.js
test
├── before.js
├── mocha.opts
└── service-test.js

Mocha configurations

Important options

  • Reporters
  • Compilers
  • Interface style (UI)
--reporter spec
--recursive
--compilers js:babel-register
--require ./test/before.js
--globals clock,proxyquire
--ui bdd

example of mocha.opts file

Write test with Full ES6

Add these packages to your package.json 

  • babel-preset-node5
  • babel-register

mocha.opts using this

--compilers js:babel-register

.babelrc using this

{
  "presets": [
    "node5"
  ]
}

Setup the code execution environme

"use strict";
 
import chai from 'chai';
import sinon from 'sinon';
import 'sinon-as-promised';
import sinonChai from 'sinon-chai';
import chaiAsPromised from 'chai-as-promised';
import lolex from 'lolex';
// prepare chai
chai.should();
chai.use(sinonChai);
chai.use(chaiAsPromised);
// expose sinon
global.sinon = sinon;
// reset clock mock for each test
setTimeout(() => {
    beforeEach(function() {
        global.clock = lolex.install();
    });
 
    afterEach(function() {
        global.clock.uninstall();
    });
});

Test suite

THE BDD

BLOCKS

describe('MyService', () => {

    let service;

    beforeEach(() => {
        service = new MyService();
    });

























});
    describe('#firstMethod(arg)', () => {

        let arg;



















    });
        context('something is wrong', () => {

            beforeEach(() => {
                arg = 'wrong arg';
            });





        });

        context('everything is ok', () => {

            /* [...] */

        });
            it('should return false', () => {
                service.firstMethod(arg).should.be.false;
            });

All functions

  • describe(.only|.skip)
  • context(.only|.skip)
  • before(Each)
  • after(Each)
  • it(.only|.skip)

Chai syntax

Assertion styles

  • Should
  • Expect
  • Assert

Should

chai.should();

foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
tea.should.have.property('flavors')
  .with.length(3);

Expect

var expect = chai.expect;

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(tea).to.have.property('flavors')
  .with.length(3);

Assert

var assert = chai.assert;

assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.lengthOf(foo, 3)
assert.property(tea, 'flavors');
assert.lengthOf(tea.flavors, 3);

in the exercises, we will use the "should" syntax

Basic case

Some Math API

  1. Start from the "setup" branch
  2. First, implement an empty test which just runs
  3. Run the test command with the "--watch" option
  4. Then, implement the test for some "sum" and "average" function
  5. Then, write the code for it

Spies and stubs

Spies & stubs

sinon.spy(service, 'first');

service.second().should.equal(2);
service.first.should.have.been.called;

service.first.restore();
sinon.stub(service, 'first').returns(41);

service.second().should.equal(42);
service.first.should.have.been.called;

service.first.restore();
const service = {
   first: () => 1,
   second: () => service.first() + 1
};

One simple service

using a spy

using a stub

a spy only monitors

a stub monitors and replaces

Case with function stubbing

Some data service

  1. Start from the "step-2-before" branch
  2. Implement a getUsers method in src/users.js
  3. getUsers uses getUser: make getUser return what you want
  4. test getUsers is behaving accordingly
    (search the way to make a stub return what you want)

Asynchronous tests

Several ways to make a block asynchronous

  • "done" arg of block function called when ok
  • block function returning a promise
  • block function as a generator (with co-mocha)
it('should eventually return true', (done) => {
    service.answer(question)
        .should.become(true)
        .and.notify(done)
    ;
});
it('should eventually return true', () => {
    return service.answer(question).should.become(true);
});
it('should eventually return true', function* () {
    const answer = yield service.answer(question);
    answer.should.be.true;
});

Writing an asynchronous stub

sinon-as-promised (or sinon >=2) lets you write async stubs

sinon.stub(service, 'getSomethingAsync').withArgs(42).resolves({ /* ... */ });

Asserting the eventual result of a promise

chai-as-promised lets you assert the eventual result

service.getSomethingAsync(42).should.become({ /* ... */ });
sinon.stub(service, 'getSomethingAsync').withArgs(null).rejects({ /* ... */ });
service.getSomethingAsync(null).should.be.rejectedWith({ /* ... */ });

Case with promises

Async case

  • Start from "step-2" branch
  • Replace getUser and getUsers by async methods (promises)
  • Update the tests
  • BONUS: use generators to implement the tests

?

JS unit tests with mocha, sinon & chai

By Alexis Tondelier

JS unit tests with mocha, sinon & chai

  • 771