Test-driven JavaScript with
Testing, 1.. 2.. 3..
Mocha, Chai, and Sinon
Test Driven Development
The idea that you write tests first, then you write code.
As a result, you think about how you will use the code before you implement it.
assert(isEven(2), true)
function isEven(n) {
return n % 2 === 0;
}
FAIL
PASS
Red, Green, Refactor
Write just enough code
Unit tests should be specific, and so should the code to make the test pass.
Once it passes, refactor, and move on to the next test.
Common pitfalls
- Forgetting to run tests frequently
- Writing too many tests at once
- Writing tests that are too large or coarse-grained
- Writing overly trivial tests, for instance omitting assertions
- Writing tests for trivial code, for instance accessors
Mocha is a feature-rich JavaScript testing framework that runs on both the browser and Node.
var assert = require("assert");
describe('Array', function(){
describe('#indexOf()', function(){
it('should return -1 when the value is not present', function(){
assert.equal(-1, [1,2,3].indexOf(5));
assert.equal(-1, [1,2,3].indexOf(0));
})
})
})
Mocha
Interfaces
Mocha offers developers different domain specific languages (DSLs) for writing tests.
- BDD
- TDD
- Exports
- QUnit
- Require
Interfaces: BDD
describe('Array', function(){
before(function(){
// ...
});
describe('#indexOf()', function(){
it('should return -1 when not present', function(){
[1,2,3].indexOf(4).should.equal(-1);
});
});
});
Interfaces: TDD
suite('Array', function(){
setup(function(){
// ...
});
suite('#indexOf()', function(){
test('should return -1 when not present', function(){
assert.equal(-1, [1,2,3].indexOf(4));
});
});
});
Interfaces: Exports
module.exports = {
before: function(){
// ...
},
'Array': {
'#indexOf()': {
'should return -1 when not present': function(){
[1,2,3].indexOf(4).should.equal(-1);
}
}
}
};
Interfaces: QUnit
function ok(expr, msg) {
if (!expr) throw new Error(msg);
}
suite('Array');
test('#length', function(){
var arr = [1,2,3];
ok(arr.length == 3);
});
test('#indexOf()', function(){
var arr = [1,2,3];
ok(arr.indexOf(1) == 0);
ok(arr.indexOf(2) == 1);
ok(arr.indexOf(3) == 2);
});
suite('String');
test('#length', function(){
ok('foo'.length == 3);
});
Interfaces: Require
var testCase = require('mocha').describe
var pre = require('mocha').before
var assertions = require('mocha').assertions
var assert = require('assert')
testCase('Array', function(){
pre(function(){
// ...
});
testCase('#indexOf()', function(){
assertions('should return -1 when not present', function(){
assert.equal([1,2,3].indexOf(4), -1);
});
});
});
Asynchronus support
Mocha makes it simple to test your asynchronous code using a done() callback function.
it('should save without error', function(done){
var user = new User('Luna');
user.save(function(err){
if (err) throw err;
done();
});
})
it('should save without error', function(done){
var user = new User('Luna');
user.save(done);
})
To make things easier the done() callback accepts an error:
File watcher support
Automatically runs tests whenever a file is changed.
$ mocha -w
Test Coverage
Mocha keeps track of which lines of code have been tested, which other libraries can access.
An assertion library for node and the browser that can be paired with any JavaScript testing framework.
Chai
Provides many plugins for testing your code, including:
- HTTP requests
- Promises
- Selenium
- DOM assertions
Should interface
Extends each object with a should property to start your assertion chain.
var foo = 'bar',
beverages = {tea: [ 'chai', 'matcha', 'oolong']};
foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
tea.should.have.property('flavors').with.length(3);
Expect interface
Extends each object with a should property to start your assertion chain.
var foo = 'bar',
beverages = {tea: [ 'chai', 'matcha', 'oolong']};
expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(beverages).to.have.property('tea').with.length(3);
// AssertionError: topic [answer]: expected 43 to equal 42.
expect(answer, 'topic [answer]').to.equal(42);
var value = undefined;
expect(value).to.be.undefined;
Assert interface
classic assert-dot notation, similiar to that packaged with node.js. This assert module, however, provides several additional tests and is browser compatible.
var foo = 'bar',
beverages = {tea: [ 'chai', 'matcha', 'oolong']};
assert.typeOf(foo, 'string', 'foo is a string');
assert.equal(foo, 'bar', 'foo equal `bar`');
assert.lengthOf(foo, 3, 'foo`s value has a length of 3');
assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');
Standalone test spies, stubs, and mocks for JavaScript.
Sinon
Provides
Spies
Stubs
Fake XML/HTTP Requests
and more
Spies
Allow you to check the conditions surrounding how a function was invoked.
it("calls the original function", function () {
var callback = sinon.spy();
var proxy = once(callback);
proxy();
assert(callback.called);
});
Stubs
Test stubs are functions (spies) with pre-programmed behavior.
it("returns the return value from the original function", function () {
var returnsFourtyTwo = sinon.stub().returns(42);
assert.equals(returnsFourtyTwo(), 42);
});
Testing Ajax
function getTodos(listId, callback) {
jQuery.ajax({
url: "/todo/" + listId + "/items",
success: function (data) {
// Node-style CPS: callback(err, data)
callback(null, data);
}
});
}
after(function () {
// When the test either fails or passes, restore the original
// jQuery ajax function (Sinon.JS also provides tools to help
// test frameworks automate clean-up like this)
jQuery.ajax.restore();
});
it("makes a GET request for todo items", function () {
sinon.stub(jQuery, "ajax");
getTodos(42, sinon.spy());
assert(jQuery.ajax.calledWithMatch({ url: "/todo/42/items" }));
});
Fake XMLHttpRequest
var xhr, requests;
before(function () {
xhr = sinon.useFakeXMLHttpRequest();
requests = [];
xhr.onCreate = function (req) { requests.push(req); };
});
after(function () {
// Like before we must clean up when tampering with globals.
xhr.restore();
});
it("makes a GET request for todo items", function () {
getTodos(42, sinon.spy());
assert.equals(requests.length, 1);
assert.match(requests[0].url, "/todo/42/items");
});
Questions?
Example repo
Testing, 1.. 2.. 3...
By Tony Gaskell
Testing, 1.. 2.. 3...
Writing tests for JavaScript using Mocha, Chai, and Sinon
- 1,661