Introduction to Mocha and Chai




Previously I mentioned that I use Mocha and Chai for testing my JavaScript applications/games. Thanks to these two packages and npm I can have a great testbase for my code which can be run very quickly just by entering npm test (which can be run inside of Emacs eshell or even fired with a key).

Definitions

Let's say that Mocha is a test framework and Chai an assertion library (which are their official definitions). Mocha does not provide assertions on its own, it just provides the scaffolding for building sets of tests, run them and display them using the reporter you decide. Chai only provides assertions: statements that check if a condition holds and throw an exception if it doesn't. Either way, it provides a nice message that Mocha will show to provide feedback about the test. This separation of concerns fits perfectly in the philosophy of node.js packages.

As an example of Mocha and Chai working together:

// TDD style
setup('module', function() {
    setup('.start()', function() {
        test('must return true', function() {
            assert.ok(module.start());
        }
    }
}

// BDD style
describe('module', function() {
    describe('.start()', function() {
        it('must return true', function() {
            expect(module.start()).to.be.ok;
        }
    }
}

// BDD style in CoffeeScript
describe 'module', =>
    describe '.start()', =>
        it 'must return true', =>
            expect(module.start()).to.be.ok;

The only Chai statements in this code are the assert/expect lines. All the suite, test, descript and it are Mocha functions. Also, as I discovered crawling through the chai-as-promised code, Mocha can be configured to use Coffeescript tests, you don't need to compile them before.

Introduction to Chai plugins

Chai provides an API to extend the set of assertions. The two plugins we have mentioned here are chai-spies and chai-as-promised. chai-spies provides methods to assert how many times a function has been called, and assert on the arguments of the calls. An example of chai-spies:

// ... Mocha boilerplate ...
// Arrange
module.method = chai.spy('method', module.method);

// Act
module.otherMethod();

// Assert
expect(module.method).to.have.been.called.twice();

The first argument to chai.spy() is the name of the method and it is optional. Thanks to BDD style this test is self-explanatory. We expect module.otherMethod() to call exactly twice module.method(), and we want it to fail otherwise.

chai-as-promised extends Chai so assertions can be made on Promises. Due to the asynchronous nature of Promises, we cannot just fire assertions at the end of a test and expect it to work properly. If we set a variable to true inside an asynchronous method, and we assert its state at the end of the method, the test is going to fail no matter what. This happens because the assertion is executed before the asynchronous code.

Mocha allows to test asynchronous behavior if you pass a function expecting an argument to the test/it functions (usually called done). Like this:

it('should ...', function(done) {
    callAsync(function() {
        // ... code code code
        done();
    });
}

The test will not succeed if done is not called. There is a timeout of 2s after which the test will fail. If there is any error thrown inside the test (such as a failed assertion), it will also fail.

However, using done to test Promises behavior starts to look hacky early, especially if we have many Promises in a single test. We can avoid it all using chai-as-promised:

it('should ...', function() {
    promise = asyncMethodThatReturnsAPromise();

    expect(promise).to.become(true); // or
    expect(promise).to.eventually.equal(5); // or
    expect(promise).to.be.rejected;
});

done is gone for good and our test is once again gracefully self-explanatory.

Plugging the plugins

To use a plugin like chai-spies with Chai we only need to call chai.use() with the required plugin as a parameter:

var chai = require('chai'),
    spies = require('chai-spies');

chai.use(spies);

This should be enough to understand how to use Mocha and Chai and its plugins in your applications (very recommended). Both have many more features than presented here (Mocha has setup/teardown methods for instance), but their documentation is very good, and I don't think rephrasing it here would help anyone. There are other features provided by other packages which are almost mandatory (mocking for instance), but I have little experience with them. If I try any, I will make a post.

I did not intend to write a tutorial, but to document how I traced a bug in chai-as-promised and made the PR. However, it was so out of the blue that I first had to present the context of the bug. Next I will write about how Chai plugins are written (context, the documentation at chaijs.com is awesome though) and this tracing I just mentioned.