Testing JavaScript          LIKE A BOSS                       Jo Cranford                       @jocranfordThursday, 19 Ju...
Y U NO JAVASCRIPT                            TESTS?Thursday, 19 July 12
BDD With Jasmine Is                         Awesome Sauce         describe("Score Calculation Behaviour", function() {    ...
BDD With Jasmine Is                         Awesome Sauce   describe("Score Calculation Behaviour", function() {          ...
BDD With Jasmine Is                         Awesome Sauce   describe("Score Calculation Behaviour", function() {          ...
BDD With Jasmine Is                         Awesome Sauce   describe("Score Calculation Behaviour", function() {          ...
Basic ShizzleThursday, 19 July 12
Jasmine In Your Project    •   Ruby Gem    •   Maven    •   Node.js    •   StandaloneThursday, 19 July 12
Ruby Gem                              > jasmine init                              > rake jasmine                       htt...
Maven    •   Searls Jasmine Maven plugin    •   Add it to the pom to run tests within the test lifecycle        phase     ...
Node.js                             > npm install jasmine-node -g                             > jasmine-node specs/       ...
Standalone    •   Download the files and copy them all across to your        project.    •   Edit the SpecRunner.html to in...
Now Let’s Write A Test   •   describe("Score Calculation Behaviour", function() {             it("should score 0 when no p...
•   expect(game.score()).not.toBe(1);                       expect(true).toBeTruthy();                       expect(false)...
.toContain()         describe("How to test for items in an Array", function() {             it("should tell me if the arra...
.toThrow()         it("should accept a single digit at a time", function() {             expect(function() {              ...
.toMatch()                       it("should compare to a regex", function () {                        expect("@jocranford"...
Before And After         beforeEach(function() {               fakeFrame = {                   addRoll: jasmine.createSpy(...
Is JavaScript Ever Really                   That Simple?Thursday, 19 July 12
What About ...    •   Asynchronous goodness    •   Interacting with teh DOMz    •   Evil Legacy Code    •   Continuous Int...
Approaches To Testing                        Asynchronous CodeThursday, 19 July 12
Let’s Load Some JSON                       [                           {                                "firstName": "Jo",...
The JavaScript Code                       var Presentation = function() {                         this.presenters = [];   ...
Easy, Right?           describe("How not to test an asynchronous function", function           () {               it("shou...
Well ... Not So Much.Thursday, 19 July 12
But This Might Work ...           describe("Still not ideal though", function () {              it("should load the presen...
A Little Detour ...Thursday, 19 July 12
Spy On An Existing Method           it("can spy on an existing method", function() {               var fakeElement = $("<d...
Spy On An Existing Method           it("can create a method for you", function() {               var fakeElement = {};    ...
Wait, There’s More ...    •   expect(spy).not.toHaveBeenCalled()    •   createSpy().andReturn(something)    •   createSpy(...
Spy On The Details    •   expect(spy).toHaveBeenCalled()    •   expect(spy.callCount).toBe(x)    •   expect(spy).toHaveBee...
... And We’re Back.                       Sooooo ... spies are great and all,                       but what if your callb...
Don’t Do This At Home.           Presentation.prototype.loadPresentersMoreSlowly = function() {               var preso = ...
Don’t Do This, Either.           it("should have loaded after three seconds, right?", function()           {              ...
But What If I Just ...           Presentation.prototype.loadPresentersMoreSlowly = function() {               var preso = ...
Now Wait, Wait ... RUN!                 it("should load the presenters", function() {                       spyOn($, "getJ...
Testing Interaction With The                    DOM    •   Do you REALLY need to?    •   Tests will have a high maintenanc...
Testing Interaction With The                  DOM            it("should display the score", function() {                se...
Legacy (untested)                        JavaScript Code    •   Long methods    •   Violation of Single Responsibility Pri...
Testing Interaction          it("should call the method on the dependency", function() {                       var depende...
If Dependencies Aren’t                             Injected ...          var LegacySomething = function() {               ...
Create Stubs          it("is a pain but not impossible", function() {                       Dependency = function() {};   ...
Continuous Integration    •   Ruby Gem    •   Maven    •   Node.js    •   Rhino (Java)Thursday, 19 July 12
Ruby Gem                        require jasmine                        load jasmine/tasks/jasmine.rake                    ...
Maven                               > mvn clean test               http://searls.github.com/jasmine-maven-plugin/Thursday,...
Node.js                             > jasmine-node specs/                       https://github.com/mhevery/jasmine-nodeThu...
Rhino    •   Download:         •   Rhino (js.jar) from Mozilla         •   env.rhino.js from www.envjs.com         •   Jas...
Rhino                 load(env.rhino.1.2.js);           Envjs.scriptTypes[text/javascript] = true;           var specFile;...
Extending Jasmine With                    Custom Matchers               it("should match the latitude and longitude", func...
Extending Jasmine With                    Custom Matchers         it("should match the latitude and longitude", function()...
Extending Jasmine With                    Custom Matchers   beforeEach(function() {       this.addMatchers({           toH...
Custom Failure Messages          toHaveLatitude: function(lat) {              this.message = function() {                 ...
Do We Really Need To Test                Everything?         DO:                         DON’T:     •   Test Behaviour and...
Tips & Gotchas    •   Tests aren’t completely independent!    •   If tests are hard to write, it’s often an indication of ...
With thanks to our sponsorsThursday, 19 July 12
Please complete your feedback           forms, and return them to the          registration desk for a chance             ...
QuestionsThursday, 19 July 12
Upcoming SlideShare
Loading in …5
×

Testing javascriptwithjasmine ddd-sydney

1,503 views
1,398 views

Published on

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

0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,503
On SlideShare
0
From Embeds
0
Number of Embeds
52
Actions
Shares
0
Downloads
10
Comments
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
  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/downloadsThursday, 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 reflect 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

×