Front End Unit Testing
Technology Previews and Proposals
Objective
To examine unit testing options, discuss their pros and cons, and work towards a decision on which tool to use.
What is Unit Testing?
-
A software development process in which the smallest testable parts of an application, called units, are individually and independently scrutinized for proper operation.
-
Small, focused tests on isolated functions or functionality
-
Meant to be fast and help isolate bugs
Tools under investigation
- Jasmine
- Mocha/Chai/Sinon
- Tape
- QUnit
- Intern
- BDD Style Syntax
- Created in 2009
- Actively maintained, sponsored by Pivotal Labs
- 9,755 Stargazers, 1,571 forks on GitHub
- Integrates with both Gulp and Grunt
Jasmine
describe("Player", function() {
var player;
var song;
beforeEach(function() {
player = new Player();
song = new Song();
});
it("should be able to play a Song", function() {
player.play(song);
expect(player.currentlyPlayingSong).toEqual(song);
//demonstrates use of custom matcher
expect(player).toBePlaying(song);
});
//...
});
beforeEach(function() {
this.addMatchers({
toBePlaying: function(expectedSong) {
var player = this.actual;
return player.currentlyPlayingSong === expectedSong &&
player.isPlaying;
}
});
});
spec-helper.js
player.spec.js
Jasmine
describe("Player", function() {
var player;
var song;
// ...
describe("when song has been paused", function() {
beforeEach(function() {
player.play(song);
player.pause();
});
it("should indicate that the song is currently paused", function() {
expect(player.isPlaying).toBeFalsy();
// demonstrates use of 'not' with a custom matcher
expect(player).not.toBePlaying(song);
});
it("should be possible to resume", function() {
player.resume();
expect(player.isPlaying).toBeTruthy();
expect(player.currentlyPlayingSong).toEqual(song);
});
});
// ...
});
Jasmine
describe("Player", function() {
var player;
var song;
// ...
// demonstrates use of spies to intercept and test method calls
it("tells the current song if the user has made it a favorite", function() {
spyOn(song, 'persistFavoriteStatus');
player.play(song);
player.makeFavorite();
expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true);
});
//demonstrates use of expected exceptions
describe("#resume", function() {
it("should throw an exception if song is already playing", function() {
player.play(song);
expect(function() {
player.resume();
}).toThrow("song is already playing");
});
});
});
Demo
Jest
- Built on top of Jasmine
- Automatically mocks dependencies
- Uses jsdom to execute tests
- Has multiple interfaces that include BDD, TDD, and QUnit styles
- Created in 2011
- Actively maintained
- 7,710 Stargazers, 1,170 forks on GitHub
- Integrates with both Gulp and Grunt
- Automatic integration with promises
- Can programmatically generate tests
- Multiple reporters
- Only has basic asserts built in, extra assertions often included by using Chai.
- Lacks built in support for spies, Sinon is generally used to achieve this functionality.
Chai jQuery assertions
$('#header').should.have.attr('foo');
expect($('body')).to.have.attr('foo', 'bar');
expect($('body')).to.have.attr('foo').match(/bar/);
$('#header').should.have.prop('disabled');
expect($('body')).to.have.prop('disabled', false);
expect($('body')).to.have.prop('value').match(/bar/);
Sinon Ajax Testing
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" }));
});
Demo
- Simple test style that doesn't pollute global scope
- Very minimalistic
- Created in 2012
- Maintenance has slowed in recent years
- 1,403 Stargazers, 95 forks on GitHub
- Integrates with both Gulp and Grunt
- Outputs results in TAP (Test-Anything-Protocol)
- Written by substack (author of browserify)
Example
var test = require('tape');
test('timing test', function (t) {
t.plan(2);
t.equal(typeof Date.now, 'function');
var start = Date.now();
setTimeout(function () {
t.equal(Date.now() - start, 100);
}, 100);
});
$ node example/timing.js
TAP version 13
# timing test
ok 1 should be equal
not ok 2 should be equal
---
operator: equal
expected: 100
actual: 107
...
1..2
# tests 2
# pass 1
# fail 1
Tap Output
- Multiple different interfaces include object, TDD, BDD, and QUnit.
- Created in 2012
- Actively maintained
- 3,064 Stargazers, 228 forks on GitHub
- Integrates with both Gulp and Grunt
- AMD by default (thus integrates well with require.js)
- Built in support for source maps and code coverage
- Supported by the Dojo foundation
Object Interface
define(function (require) {
var tdd = require('intern!tdd');
tdd.suite('Suite name', function () {
tdd.before(function () {
// executes before suite starts
});
tdd.after(function () {
// executes after suite ends
});
tdd.beforeEach(function () {
// executes before each test
});
tdd.afterEach(function () {
// executes after each test
});
tdd.test('Test foo', function () {
// a test case
});
});
ES6 Object Interface
TDD Inteface
define(function (require) {
var tdd = require('intern!tdd');
tdd.suite('Suite name', function () {
tdd.before(function () {
// executes before suite starts
});
tdd.after(function () {
// executes after suite ends
});
tdd.beforeEach(function () {
// executes before each test
});
tdd.afterEach(function () {
// executes after each test
});
tdd.test('Test foo', function () {
// a test case
});
});
BDD Inteface
define(function (require) {
var bdd = require('intern!bdd');
bdd.describe('the thing being tested', function () {
bdd.before(function () {
// executes before suite starts
});
bdd.after(function () {
// executes after suite ends
});
bdd.beforeEach(function () {
// executes before each test
});
bdd.afterEach(function () {
// executes after each test
});
bdd.it('should do foo', function () {
// a test case
});
});
});
QUnit
- Created in 2008
- Very Actively maintained by the JQuery team
- 3,293 Stargazers, 675 forks on GitHub
- Integrates with both Gulp and Grunt
Questions?
Front End Unit Testing
By Justin Bennett
Front End Unit Testing
- 702