3. BDD With Jasmine Is
Awesome Sauce
describe("Score Calculation Behaviour", function() {
it("should score 0 when no pins are knocked down", function() {
var game = new BowlingGame(10);
game.roll(0);
expect(game.score()).toBe(0);
});
});
Thursday, 19 July 12
4. BDD With Jasmine Is
Awesome Sauce
describe("Score Calculation Behaviour", function() {
it("should score 0 when no pins are knocked down", function() {
var game = new BowlingGame(10);
game.roll(0);
expect(game.score()).toBe(0);
});
});
Thursday, 19 July 12
5. BDD With Jasmine Is
Awesome Sauce
describe("Score Calculation Behaviour", function() {
it("should score 0 when no pins are knocked down", function() {
var game = new BowlingGame(10);
game.roll(0);
expect(game.score()).toBe(0);
});
});
Thursday, 19 July 12
6. BDD With Jasmine Is
Awesome Sauce
describe("Score Calculation Behaviour", function() {
it("should score 0 when no pins are knocked down", function() {
var game = new BowlingGame(10);
game.roll(0);
expect(game.score()).toBe(0);
});
});
Thursday, 19 July 12
10. Maven
• Searls Jasmine Maven plugin
• Add it to the pom to run tests within the test lifecycle
phase
> mvn jasmine:bdd
http://searls.github.com/jasmine-maven-plugin/
Thursday, 19 July 12
12. Standalone
• Download the files and copy them all across to your
project.
• Edit the SpecRunner.html to include your JavaScript
source and tests.
• Open in your favourite browser.
https://github.com/pivotal/jasmine/downloads
Thursday, 19 July 12
13. Now Let’s Write A Test
• describe("Score Calculation Behaviour", function() {
it("should score 0 when no pins are knocked down", function() {
var game = new BowlingGame(10);
game.roll(0);
expect(game.score()).toBe(0);
});
});
Thursday, 19 July 12
15. .toContain()
describe("How to test for items in an Array", function() {
it("should tell me if the array contains an item", function() {
var theArray = [1, 2, 3];
expect(theArray).toContain(1);
expect(theArray).not.toContain(4);
});
});
Thursday, 19 July 12
16. .toThrow()
it("should accept a single digit at a time", function() {
expect(function() {
calculator.enterDigit("2");
}).not.toThrow();
});
it("should not accept letters", function() {
expect(function() {
calculator.enterDigit("a");
}).toThrow("Only accepts numbers");
});
Thursday, 19 July 12
17. .toMatch()
it("should compare to a regex", function () {
expect("@jocranford").toMatch("@([A-Za-z0-9_]+)");
});
Thursday, 19 July 12
18. Before And After
beforeEach(function() {
fakeFrame = {
addRoll: jasmine.createSpy("Add roll"),
isComplete: jasmine.createSpy("Is complete"),
setSpareBonus: jasmine.createSpy("spare bonus"),
setStrikeBonus: jasmine.createSpy("strike bonus"),
score: jasmine.createSpy("score")
};
});
Thursday, 19 July 12
20. What About ...
• Asynchronous goodness
• Interacting with teh DOMz
• Evil Legacy Code
• Continuous Integration
• Clean readable tests that reflect your domain
Thursday, 19 July 12
23. The JavaScript Code
var Presentation = function() {
this.presenters = [];
};
Presentation.prototype.loadPresenters = function() {
var presenters = this.presenters;
$.getJSON("people.json", function(data) {
$.each(data, function(idx, person) {
presenters.push(person);
});
});
};
Thursday, 19 July 12
24. Easy, Right?
describe("How not to test an asynchronous function", function
() {
it("should load the presenters", function () {
var presentation = new Presentation();
presentation.loadPresenters();
expect(presentation.presenters.length).toBe(2);
});
});
Thursday, 19 July 12
26. But This Might Work ...
describe("Still not ideal though", function () {
it("should load the presenters", function () {
spyOn($, "getJSON").andCallFake(function (url, callback) {
callback([{},{}]);
})
var presentation = new Presentation();
presentation.loadPresenters();
expect(presentation.presenters.length).toBe(2);
});
});
Thursday, 19 July 12
28. Spy On An Existing Method
it("can spy on an existing method", function() {
var fakeElement = $("<div style='display:none'></div>");
spyOn(fakeElement, 'show');
var toggleable = new Toggleable(fakeElement);
toggleable.toggle();
expect(fakeElement.show).toHaveBeenCalled();
});
Thursday, 19 July 12
29. Spy On An Existing Method
it("can create a method for you", function() {
var fakeElement = {};
fakeElement.css = function() {};
fakeElement.show = jasmine.createSpy("Show spy");
var toggleable = new Toggleable(fakeElement);
toggleable.toggle();
expect(fakeElement.show).toHaveBeenCalled();
});
Thursday, 19 July 12
30. Wait, There’s More ...
• expect(spy).not.toHaveBeenCalled()
• createSpy().andReturn(something)
• createSpy().andCallFake(function() {})
• createSpy().andCallThrough()
Thursday, 19 July 12
31. Spy On The Details
• expect(spy).toHaveBeenCalled()
• expect(spy.callCount).toBe(x)
• expect(spy).toHaveBeenCalledWith()
• Tip: use jasmine.any(Function/Object) for parameters
you don’t care about
Thursday, 19 July 12
32. ... And We’re Back.
Sooooo ... spies are great and all,
but what if your callback function
takes a while to run?
Thursday, 19 July 12
33. Don’t Do This At Home.
Presentation.prototype.loadPresentersMoreSlowly = function() {
var preso = this;
$.getJSON("people.json", function(data) {
setTimeout(function() {
$.each(data, function(idx, person) {
preso.presenters.push(person);
});
}, 2000);
});
};
Thursday, 19 July 12
34. Don’t Do This, Either.
it("should have loaded after three seconds, right?", function()
{
spyOn($, "getJSON").andCallFake(function(url, callback) {
callback([{}, {}]);
})
var presentation = new Presentation();
presentation.loadPresentersMoreSlowly();
setTimeout(function() {
expect(presentation.presenters.length).toBe(2);
}, 3000);
});
Thursday, 19 July 12
35. But What If I Just ...
Presentation.prototype.loadPresentersMoreSlowly = function() {
var preso = this;
$.getJSON("people.json", function(data) {
setTimeout(function() {
$.each(data, function(idx, person) {
preso.presenters.push(person);
});
preso.presentersHaveLoaded = true;
}, 2000);
});
};
Thursday, 19 July 12
36. Now Wait, Wait ... RUN!
it("should load the presenters", function() {
spyOn($, "getJSON").andCallFake(function(url, callback) {
callback([{}, {}]);
})
var presentation = new Presentation();
presentation.loadPresentersMoreSlowly();
waitsFor(function() {
return presentation.presentersHaveLoaded;
}, "presenters have loaded");
runs(function() {
expect(presentation.presenters.length).toBe(2);
});
});
Thursday, 19 July 12
37. Testing Interaction With The
DOM
• Do you REALLY need to?
• Tests will have a high maintenance cost
• Instead separate logic from view and test logic
• Use templates for the view
Thursday, 19 July 12
38. Testing Interaction With The
DOM
it("should display the score", function() {
setFixtures("<div id='score'></div>");
var bowlingGameView = new BowlingGameView();
bowlingGameView.showScore(100);
expect($("#score").text()).toBe("Your current score is 100");
});
https://github.com/velesin/jasmine-jquery
Thursday, 19 July 12
39. Legacy (untested)
JavaScript Code
• Long methods
• Violation of Single Responsibility Principle
• Side effects
• Lack of dependency injection
• Lots of new X()
• Unclear intentions
Thursday, 19 July 12
40. Testing Interaction
it("should call the method on the dependency", function() {
var dependency = {};
dependency.method = jasmine.createSpy();
var myObject = new Something(dependency);
myObject.doSomething();
expect(dependency.method).toHaveBeenCalled();
});
Thursday, 19 July 12
41. If Dependencies Aren’t
Injected ...
var LegacySomething = function() {
this.doSomething = function() {
var dependency = new Dependency();
dependency.method();
};
};
Thursday, 19 July 12
42. Create Stubs
it("is a pain but not impossible", function() {
Dependency = function() {};
Dependency.prototype.method = jasmine.createSpy()
var myObject = new LegacySomething();
myObject.doSomething();
expect(Dependency.prototype.method).toHaveBeenCalled();
});
Thursday, 19 July 12
45. Maven
> mvn clean test
http://searls.github.com/jasmine-maven-plugin/
Thursday, 19 July 12
46. Node.js
> jasmine-node specs/
https://github.com/mhevery/jasmine-node
Thursday, 19 July 12
47. Rhino
• Download:
• Rhino (js.jar) from Mozilla
• env.rhino.js from www.envjs.com
• Jasmine console reporter from Larry Myers Jasmine
Reporters project (github)
http://www.build-doctor.com/2010/12/08/javascript-bdd-jasmine/
Thursday, 19 July 12
48. Rhino
load('env.rhino.1.2.js');
Envjs.scriptTypes['text/javascript'] = true;
var specFile;
for (i = 0; i < arguments.length; i++) {
specFile = arguments[i];
console.log("Loading: " + specFile);
window.location = specFile
}
> java -jar js.jar -opt -1 env.bootstrap.js ../SpecRunner.html
Thursday, 19 July 12
49. Extending Jasmine With
Custom Matchers
it("should match the latitude and longitude", function() {
var pointOnMap = { latitude: "51.23", longitude: "-10.14" };
expect(pointOnMap.latitude).toBe("51.23");
expect(pointOnMap.longitude).toBe("-10.14");
});
it("should match the latitude and longitude", function() {
var pointOnMap = { latitude: "51.23", longitude: "-10.14" };
expect(pointOnMap).toHaveLatitude("51.23");
expect(pointOnMap).toHaveLongitude("-10.14");
});
Thursday, 19 July 12
50. Extending Jasmine With
Custom Matchers
it("should match the latitude and longitude", function() {
var pointOnMap = { latitude: "51.23", longitude: "-10.14" };
expect(pointOnMap).toHaveLatLongCoordinates("51.23", "-10.14");
});
Thursday, 19 July 12
52. Custom Failure Messages
toHaveLatitude: function(lat) {
this.message = function() {
return "Expected Latitude " + this.actual.latitude
+ " to be " + lat;
};
return this.actual.latitude === lat;
}
Thursday, 19 July 12
53. Do We Really Need To Test
Everything?
DO: DON’T:
• Test Behaviour and • Over-test DOM interaction
Functionality
• Test Getters and Setters
• Test the places where the
bad bugs bite • Test functionality in third
party libraries
• Write tests that are
expensive to maintain
Thursday, 19 July 12
54. Tips & Gotchas
• Tests aren’t completely independent!
• If tests are hard to write, it’s often an indication of a smell
in the code
• Automate creation of SpecRunner.html
• Run tests in different browsers
Thursday, 19 July 12