Jonathan Newton

JavaScript unit testing with Mocha, and Chai.

JavaScript unit testing is essential.
When I hear something is untested I think vulnerable, or technical debt and you should to.

This is run down of mocha and chai pulled from a presentation I did at the Hydrographic office.

Mocha

Mocha is a testing library for Node.js, created to be a simple, extensible, and fast testing suite. It’s used for unit and integration testing, and it’s a great candidate for BDD (Behavior Driven Development).

Mocha gives us the ability to describe the features that we are implementing by giving us a describe function that encapsulates our expectations.

  • Asynchronous, Synchronous
  • Dynamic Test Generation
  • Custom Reporters
  • Retry Policies
  • Test Analytics
  • Test interface styles

Mocha Interfaces

BDD Interface

The first argument is a simple string that describes the feature, while the second argument is a function that represents the body of the description.

The describe is your test suite,
The context is the body of your test suite,
The it is the actually test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const should = require('chai').should()
let jam = { types: [ 'Blueberry', 'Strawberry', 'Raspberry', 'Plum' ] };

describe('Jam', function() {
before(function() {
//...
});

context('Root', function() {

it('Should be an object', function() {
jam.should.be.an('object');
});

});
});

TDD Interface

Similar syntax to the BDD interface but the instead of context we have suite inside of a suite signalling an inner suite, all so instead of the ‘it’ syntax we have test it serves the same purpose just a bit more verbose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const assert = chai.assert;
let jam = { types: [ 'Blueberry', 'Strawberry', 'Raspberry', 'Plum' ] };

suite('Jam', function() {
setup(function() {
//...
});

suite('Root', function() {

test('Is an object', function() {
assert.isObject(jam, 'Jam is an object');
});

});
});

Qunit Interface

For those coming from QUnit to Mocha this is ideal.

QUnit favours moving out and nested suites into the same tab.
And your ‘suites’ look more like .net regions the runner will automatically group tests based on the the last declared suite

1
2
3
4
5
6
7
8
9

const expect = chai.expect;
let jam = { types: [ 'Blueberry', 'Strawberry', 'Raspberry', 'Plum' ] };

suite('Jam');

test('Root', function() {
expect(jam).to.be.an('object');
});

Chai

Chai is an assertion library.

  • BDD syntax
  • Integrates with test frameworks
  • Assertion styles

Chai is not a framework it’s designed to work along side a framework, how you choose to run your tests or what technology you want to use is entirely up to you chai give you the option to keep you assertion library and your framework separate so if you wanted to change your framework you wouldn’t have to rewrite any of your tests, this is really powerful when we start looking at some of the plugins and async capability

Chai Styles

Should style

Should allows you to chain result expectations whilst keeping the test assert clean and verbose.​

The BDD styles are expect and should.

Both use the same chainable language to construct assertions, but they differ in the way an assertion is initially constructed.

1
2
3
4
5
6
7
8
const should = require('chai').should()

let jam = { types: [ 'Blueberry', 'Strawberry', 'Raspberry', 'Plum' ] };

jam.should.be.an('object');
jam.should.have.property('types').with.lengthOf(4);
jam.types.should.be.an('array');
jam.types.should.be.an('array').that.does.not.include('Peanut Butter');

Expect style

Expect covers the same functionality but it does a lot of the chaining for you.

You pass your test object into the expect then chain your result expectations.

1
2
3
4
5
6
7
8
const expect = chai.expect;

let jam = { types: [ 'Blueberry', 'Strawberry', 'Raspberry', 'Plum' ] };

expect(jam).to.be.an('object');
expect(jam).to.have.property('types').with.lengthOf(4);
expect(jam.types).to.be.an('array');
expect(jam.types).to.be.an('array').that.does.not.include('Peanut Butter');

Assert style

This is a very common assertion style and you see it include with alot of frameworks mocha comes with its own assert library, qunit and Jasmine also use this style.

Assert is not as chainable as Should or Expect but can handle more complex assertions and is more like the syntax found in other libraries

1
2
3
4
5
6
7
8
9
const assert = chai.assert;

let jam = { types: [ 'Blueberry', 'Strawberry', 'Raspberry', 'Plum' ] };

assert.isObject(jam, 'Jam is an object');
assert.property(jam, 'types');
assert.isArray(jam.types, 'Jam types is an array');
assert.lengthOf(jam.types, 4, '4 items in array');
assert.doesNotHaveAnyKeys(jam.types, 'Peanut Butter');

Stubs and Spies

Testing functions that return jquery elements, http request, data, asynchronous promises, ect… would be pretty difficult without stubs and spies, they allow us to force a response or watch interactions between objects

Spies

A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. There are two types of spies: Some are anonymous functions, while others wrap methods that already exist in the system under test.

When to Use Spies?

Spies are used to get information about function calls. For example, a spy can tell us how many times a function was called, what arguments each call had, what values were returned, what errors were thrown, etc.

As such, a spy is a good choice whenever the goal of a test is to verify something happened. Combined with Sinon’s assertions, we can check many different results by using a simple spy.

Stubs

Test stubs are functions with pre-programmed behaviour.

They support the all of the Chai assertion library in addition to methods which can be used to alter the stub’s behaviour.

As spies, stubs can be either anonymous, or wrap existing functions. When wrapping an existing function with a stub, the original function is not called.

When to Use Stub?

Control a method’s behavior from a test to force the code down a specific path. Examples include forcing a method to throw an error in order to test error handling.

When you want to prevent a specific method from being called directly (possibly because it triggers undesired behavior, such as a XMLHttpRequest or similar).

Plugins

Plugins are lightweight bolt-on’s for chai that help extend functionality without changing the assert chains.

Plugins give you continuity across your entire test suite.

Chai has a massive collection of 3rd party and vendor specific plugins.

1
2
chai.use(chaiHttp);
chai.use(sinonChai);

Chai and http mocks

Chai HTTP provides an interface for live integration testing web apps using Chai’s chain-able asserts this you test server routes.