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
support repository: https://github.com/atondelier/coding-dojo-mocha
Setup
Stack
- mocha: the test suite framework
- chai: use chai syntax for assertions
- sinon: write spies, stubs, the easy way
- sinon-as-promised: write stubs that mimic promises (or sinon >= 2)
- sinon-chai: extend chai with sinon js spies flags
- chai-as-promised: extend chai with assertions on promises
- lolex: mock the clock
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.jsMocha configurations
Important options
- Reporters
- Compilers
- Interface style (UI)
--reporter spec
--recursive
--compilers js:babel-register
--require ./test/before.js
--globals clock,proxyquire
--ui bddexample 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
- Start from the "setup" branch
- First, implement an empty test which just runs
- Run the test command with the "--watch" option
- Then, implement the test for some "sum" and "average" function
- Then, write the code for it
support repository: https://github.com/atondelier/coding-dojo-mocha
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
- Start from the "step-2-before" branch
- Implement a getUsers method in src/users.js
- getUsers uses getUser: make getUser return what you want
- test getUsers is behaving accordingly
(search the way to make a stub return what you want)
support repository: https://github.com/atondelier/coding-dojo-mocha
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
support repository: https://github.com/atondelier/coding-dojo-mocha
?
JS unit tests with mocha, sinon & chai
By Alexis Tondelier
JS unit tests with mocha, sinon & chai
- 771