- Senior UI Developer at Hobsons, where we build cool stuff to help teachers, students, and school counselors
- Author of an upcoming book on Jasmine
- Publisher of A Drip of JavaScript, a weekly JavaScript newsletter
Like...
Raise your hands for yes.
describe("Addition function", function() { it("should add numbers", function() { expect(add(1, 1)).toBe(2); }); });
The practice of writing your tests first, so that they can guide your implementation.
The steps of doing TDD are:
They're the same thing.
Jasmine uses the term "specs" to emphasize that they should be written before you start coding.
describe
block (they can be nested)it
block which contains a specexpect
which is passed an expression (called the "actual" value)toBe
and toBeGreaterThan
)describe("Calculator", function() { describe("Addition function", function() { it("should add numbers", function() { expect(add(1, 1)).toBe(2); expect(add(2, 2)).toBeGreaterThan(3); }); }); });
Is just an ordinary HTML file that you load in your browser.
Include the Jasmine library, your sources, your specs, and you have a test environment.
<link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css"> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script> <!-- include source files here... --> <script type="text/javascript" src="src/Player.js"></script> <script type="text/javascript" src="src/Song.js"></script> <!-- include spec files here... --> <script type="text/javascript" src="spec/SpecHelper.js"></script> <script type="text/javascript" src="spec/PlayerSpec.js"></script>
We're going to write a mini string library which has the following functions.
n
of a string.stringUtil
library.firstWord
function.describe("stringUtil", function() { describe("firstWord", function() { // Specs go here. }); });
describe("stringUtil", function() { describe("firstWord", function() { it("should return the first word of a string", function () { expect(stringUtil.firstWord("one two")).toBe("one"); }); }); });
stringUtil
isn't defined.var stringUtil = {};
stringUtil
doesn't have a firstWord
methodvar stringUtil = { firstWord: function() { // Not doing anything yet. } };
var stringUtil = { firstWord: function(text) { var textWords = text.split(" "); return textWords[0]; } };
var stringUtil = { firstWord: function(text) { return text.split(" ")[0]; } };
nthWord
describe("stringUtil", function() { describe("firstWord", function() { it("should return the first word of a string", function () { expect(stringUtil.firstWord("one two")).toBe("one"); }); }); describe("nthWord", function() { it("should return the nth word of a string", function () { expect(stringUtil.nthWord("one two", 1)).toBe("one"); expect(stringUtil.nthWord("one two", 2)).toBe("two"); }); }); });
stringUtil
doesn't have a nthWord
methodnthWord
var stringUtil = { firstWord: function(text) { return text.split(" ")[0]; }, nthWord: function(text, i) { return text.split(" ")[i - 1]; } };
nthWord
method is so simple that there isn't really anything to refactor in the method itself.var stringUtil = { firstWord: function(text) { return text.split(" ")[0]; }, nthWord: function(text, i) { return text.split(" ")[i - 1]; } };
firstWord
describe("firstWord", function() { it("should return the first word of a string", function () { expect(stringUtil.firstWord("one two")).toBe("one"); }); it("should delegate its logic to nthWord", function () { spyOn(stringUtil, "nthWord"); stringUtil.firstWord("one two"); expect(stringUtil.nthWord).toHaveBeenCalled(); }); });
Jasmine spies are like double-agents. They replace an ordinary function and report back what they're doing.
They can:
nthWord
isn't being calledfirstWord
to delegate to nthWord
var stringUtil = { firstWord: function(text) { return this.nthWord(text, 1); }, nthWord: function(text, i) { return text.split(" ")[i - 1]; } };
var stringUtil = { firstWord: function(text) { return this.nthWord(text, 1); }, nthWord: function(text, i) { return text.split(" ")[i - 1]; } };
Matcher | Description |
---|---|
toBe | Compares actual and expected with === |
toEqual | Compares simple object literals that === cannot |
toMatch | Compares actual value against a regular expression |
toBeDefined | Checks to see if the actual value is defined |
toBeUndefined | Checks to see if the actual value is undefined |
toBeNull | Checks to see if the actual value is null |
toBeTruthy | Checks to see if the actual value coerces to true |
Matcher | Description |
---|---|
toBeFalsy | Checks to see if the actual value coerces to false |
toContain | Checks to see if an array contains the expected value |
toBeLessThan | Self-explanatory |
toBeGreaterThan | Self-explanatory |
toBeCloseTo | For precision math comparisons on floating point numbers |
toThrow | Checks to see if an error is thrown |
toHaveBeenCalled | Checks to see if the spy was called |
toHaveBeenCalledWith | Checks to see if the spy was called with the expected parameters |
This example checks the last element in an array to see if it matches the expected value.
this.addMatchers({ toEndWith: function(expected) { return this.actual[this.actual.length - 1] === expected; } });
expect(red).toBe(jasmine.any(Color));
describe("stringUtil", function() { describe("firstWord", function() { it("should return the first word of a string", function () { expect(stringUtil.firstWord("one two")).toBe("one"); }); it("should delegate its logic to nthWord", function () { spyOn(stringUtil, "nthWord"); stringUtil.firstWord("one two"); expect(stringUtil.nthWord).toHaveBeenCalled(); }); }); describe("nthWord", function() { it("should return the nth word of a string", function () { expect(stringUtil.nthWord("one two", 1)).toBe("one"); expect(stringUtil.nthWord("one two", 2)).toBe("two"); }); }); });
describe "stringUtil", -> describe "firstWord", -> it "should return the first word of a string", -> expect(stringUtil.firstWord("one two")).toBe "one" it "should delegate its logic to nthWord", -> spyOn stringUtil, "nthWord" stringUtil.firstWord "one two" expect(stringUtil.nthWord).toHaveBeenCalled() describe "nthWord", -> it "should return the nth word of a string", -> expect(stringUtil.nthWord("one two", 1)).toBe "one" expect(stringUtil.nthWord("one two", 2)).toBe "two"
CoffeeScript specs end up being a lot cleaner and easier to read.
It depends on:
module.exports = function(grunt) { grunt.loadNpmTasks("grunt-contrib-jasmine"); grunt.loadNpmTasks("grunt-contrib-watch"); grunt.initConfig({ jasmine: { test: { src: ["lib/jquery.min.js", "src/mySource.js"], options: { specs: "spec/mySpec.js" } } }, watch: { files: ["src/mySource.js", "spec/mySpec.coffee"], tasks: ["default"] } }); grunt.registerTask("default", ["jasmine:test"]); };
novawebdev
. It will be good for the next month.You can find it at: adripofjavascript.com
You can find them at: joshuacc.github.com/tdd-with-jasmine/
Source code at: github.com/joshuacc/tdd-with-jasmine/
Feel free to use them to present at your company.
A link back would be nice. :-)