Upcoming SlideShare
×

Testing javascriptwithjasmine ddd-sydney

1,503 views
1,398 views

Published on

Slides from my presentation on June 30 at Developer Developer Developer Sydney

2 Likes
Statistics
Notes
• Full Name
Comment goes here.

Are you sure you want to Yes No
• Be the first to comment

Views
Total views
1,503
On SlideShare
0
From Embeds
0
Number of Embeds
52
Actions
Shares
0
10
0
Likes
2
Embeds 0
No embeds

No notes for slide

Testing javascriptwithjasmine ddd-sydney

1. 1. Testing JavaScript LIKE A BOSS Jo Cranford @jocranfordThursday, 19 July 12
2. 2. Y U NO JAVASCRIPT TESTS?Thursday, 19 July 12
3. 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. 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. 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. 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
7. 7. Basic ShizzleThursday, 19 July 12
8. 8. Jasmine In Your Project • Ruby Gem • Maven • Node.js • StandaloneThursday, 19 July 12
9. 9. Ruby Gem > jasmine init > rake jasmine https://github.com/pivotal/jasmine-gemThursday, 19 July 12
10. 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
11. 11. Node.js > npm install jasmine-node -g > jasmine-node specs/ https://github.com/mhevery/jasmine-nodeThursday, 19 July 12
13. 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
14. 14. • expect(game.score()).not.toBe(1); expect(true).toBeTruthy(); expect(false).toBeFalsy(); expect(undefined).toBeUndefined(); expect({}).toBeDefined(); expect(null).toBeNull(); expect(1).toBeLessThan(2); expect(2).toBeGreaterThan(1); expect(new Date(2012, 1, 1).toBeLessThan(new Date(2012, 2, 1)); expect("aaa").toBeLessThan("bbb");Thursday, 19 July 12
15. 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. 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. 17. .toMatch() it("should compare to a regex", function () { expect("@jocranford").toMatch("@([A-Za-z0-9_]+)"); });Thursday, 19 July 12
18. 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
19. 19. Is JavaScript Ever Really That Simple?Thursday, 19 July 12
20. 20. What About ... • Asynchronous goodness • Interacting with teh DOMz • Evil Legacy Code • Continuous Integration • Clean readable tests that reﬂect your domainThursday, 19 July 12
21. 21. Approaches To Testing Asynchronous CodeThursday, 19 July 12
22. 22. Let’s Load Some JSON [ { "firstName": "Jo", "lastName": "Cranford", "company": "Atlassian" }, { "firstName": "Rachel", "lastName": "Laycock", "company": "ThoughtWorks" } ]Thursday, 19 July 12
23. 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. 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
25. 25. Well ... Not So Much.Thursday, 19 July 12
26. 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
27. 27. A Little Detour ...Thursday, 19 July 12
28. 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. 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. 30. Wait, There’s More ... • expect(spy).not.toHaveBeenCalled() • createSpy().andReturn(something) • createSpy().andCallFake(function() {}) • createSpy().andCallThrough()Thursday, 19 July 12
31. 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 aboutThursday, 19 July 12
32. 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. 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. 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. 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. 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. 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 viewThursday, 19 July 12
38. 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-jqueryThursday, 19 July 12
39. 39. Legacy (untested) JavaScript Code • Long methods • Violation of Single Responsibility Principle • Side effects • Lack of dependency injection • Lots of new X() • Unclear intentionsThursday, 19 July 12
40. 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. 41. If Dependencies Aren’t Injected ... var LegacySomething = function() { this.doSomething = function() { var dependency = new Dependency(); dependency.method(); }; };Thursday, 19 July 12
42. 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
43. 43. Continuous Integration • Ruby Gem • Maven • Node.js • Rhino (Java)Thursday, 19 July 12
44. 44. Ruby Gem require jasmine load jasmine/tasks/jasmine.rake > rake jasmine:ci https://github.com/pivotal/jasmine-gemThursday, 19 July 12
45. 45. Maven > mvn clean test http://searls.github.com/jasmine-maven-plugin/Thursday, 19 July 12
46. 46. Node.js > jasmine-node specs/ https://github.com/mhevery/jasmine-nodeThursday, 19 July 12
47. 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. 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.htmlThursday, 19 July 12
49. 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. 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
51. 51. Extending Jasmine With Custom Matchers beforeEach(function() { this.addMatchers({ toHaveLatitude: function(lat) { return this.actual.latitude === lat; }, toHaveLongitude: function(lat) { return this.actual.latitude === lat; }, toHaveLatLongCoordinates: function(lat, lng) { return (this.actual.latitude === lat && this.actual.longitude === lng); } }); });Thursday, 19 July 12
52. 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. 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 maintainThursday, 19 July 12
54. 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 browsersThursday, 19 July 12
55. 55. With thanks to our sponsorsThursday, 19 July 12
56. 56. Please complete your feedback forms, and return them to the registration desk for a chance to win a Nokia LumiaThursday, 19 July 12
57. 57. QuestionsThursday, 19 July 12