Unit testingJavaScript
using Mocha and Node.js
Josh MockSenior JavaScript architect at EmmaTwitter: Email:
What is unit testing?Write code to test codeEnsures code works as expectedGranular, single-focus assertionsNot a substitute for QA
Why unit test?ConfidenceEasier refactoringLess regressionLess complexityTDD is fun!
Install Node.jsnode.js.org/download/
OS X (with Homebrew installed):brew install node
Install Mochanpm install -g mocha
Test some code!var Car = function () { this.make = "Honda"; this.model = "Civic";};
var assert = require("assert");
describe("Car", function () { describe("constructor", function () { it("should default the car to be a Honda Civic"); });
describe("makeAndModel", function () { it("should return a string containing the make and model"); });});
Run, tests, run
mocha path/to/test/file.js
How to write good testsTest results, not internalsOne focus per testTesting DOM changes is bold
How to write testable code
Simple, single-purpose functions// badvar numbers = { list: [1, 2, 3], add: function (newNum) { this.list.push(newNum); this.list.sort(); }};
// goodvar numbers = { list: [1, 2, 3], add: function (newNum) { this.list.push(newNum); }, sort: function () { this.list.sort(); }};
Avoid tight coupling of componentsvar numbers = { list: [1, 2, 3], add: function (newNum) { this.list.push(newNum); }};
// badvar math = { add: function () { var total = 0; for (var i = 0; i < numbers.list.length; i++) { total += numbers.list[i]; } return total; }, average: function () { return this.add() / numbers.list.length; }};
alert(math.average());
// goodvar math = { add: function (numList) { var total = 0; for (var i = 0; i < numList.length; i++) { total += numList[i]; } return total;
Separate business logic from UI(and avoid anonymous functions/callbacks)
var numbers = [2, 4, 1, 3, 5];
// bad$("a.sort-numbers").on("click", function (e) { e.preventDefault(); numbers.sort();});
// goodvar sortNumbers = function (e) { e && e.preventDefault && e.preventDefault(); numbers.sort();};$("a.sort-numbers").on("click", sortNumbers);
Advanced stuff!
Asynchronous tests
var asyncSort = function (numbers, callback) { setTimeout(function () { callback(numbers.sort()); }, 10);};
define("asyncSort", function () { it("should sort my numbers", function (done) { asyncSort([1, 3, 2], function (result) { assert.deepEqual(result, [1, 2, 3]); done(); }); });});
Sinon.jsnpm install -g sinon
Spiesvar sinon = require("sinon");
it("runs jQuery.ajax", function () { sinon.spy($, "ajax"); doAjaxCall(); assert($.ajax.calledOnce); $.ajax.restore();});
it("does some thing that takes forever", function () { someGlobal.slowFunction = sinon.spy(); callSlowFunction(); assert.equal(someGlobal.slowFunction.callCount, 1); assert(someGlobal.slowFunction.calledWith(1, "two", 3));});
Stubsvar sinon = require("sinon");
it("returns the age of a person with data stored in the database", function () { Database.get = sinon.stub().returns({ name: "Joe", age: 33 });
var getAge = function () { return Database.get("Joe").age; };
assert.equals(getAge(), 33);});
Mocksvar sinon = require("sinon");
it("should get the desired car from the database", function () { var mock = sinon.mock(Database);
mock .expects("getCar") .withExactArgs("Honda Civic") .once()
var car = new Car(); car.get("Honda Civic");
assert(mock.verify());});
Fake timersvar sinon = require("sinon");
it("should save after 30 seconds", function () { var clock = sinon.useFakeTimers();
sinon.spy($, "ajax");
delayedSave(); clock.tick(30001);
assert($.fn.ajax.called);
$.ajax.restore();});
jsdom and node-jqueryTest browser-dependent codeMake Node think it's a browserTest jQuery DOM manipulationsGo through all stages of grief getting it to work
Ponder using a browser-based framework instead
Installnpm install -g jsdom && npm install -g jquery
Set upGLOBAL.document = require("jsdom").jsdom();GLOBAL.window = document.createWindow();GLOBAL.$ = GLOBAL.jQuery = require("jquery").create(window);
Useit("should change div background color to blue", function () { $("body").html('<div id="mydiv"></div>'); $("#mydiv").css("background", "blue"); assert.equal($("#mydiv").css("background"), "blue");});
No headless browserNo GUI running in backgroundNo guarantees
THE ENDQuestions?
Twitter: Email:
github.com/JoshMock/mocha-node-slides