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() {    ...
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() {                 ...
Thursday, 19 July 12
Upcoming SlideShare
Loading in …5
×

Testing javascriptwithjasmine sydjs

1,096 views
999 views

Published on

Slides from my presentation on July 18 at Syd JS

Published in: Technology, Business
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,096
On SlideShare
0
From Embeds
0
Number of Embeds
33
Actions
Shares
0
Downloads
10
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Testing javascriptwithjasmine sydjs

  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. Is JavaScript Ever Really That Simple?Thursday, 19 July 12
  5. 5. What About ... • Asynchronous goodness • Interacting with teh DOMz • Evil Legacy Code • Continuous Integration • Clean readable tests that reflect your domainThursday, 19 July 12
  6. 6. Approaches To Testing Asynchronous CodeThursday, 19 July 12
  7. 7. Let’s Load Some JSON [ { "firstName": "Jo", "lastName": "Cranford", "company": "Atlassian" }, { "firstName": "Rachel", "lastName": "Laycock", "company": "ThoughtWorks" } ]Thursday, 19 July 12
  8. 8. 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
  9. 9. 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
  10. 10. Well ... Not So Much.Thursday, 19 July 12
  11. 11. 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
  12. 12. A Little Detour ...Thursday, 19 July 12
  13. 13. 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
  14. 14. 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
  15. 15. Wait, There’s More ... • expect(spy).not.toHaveBeenCalled() • createSpy().andReturn(something) • createSpy().andCallFake(function() {}) • createSpy().andCallThrough()Thursday, 19 July 12
  16. 16. 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
  17. 17. ... 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
  18. 18. 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
  19. 19. 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
  20. 20. 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
  21. 21. 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
  22. 22. 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
  23. 23. 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
  24. 24. 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
  25. 25. 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
  26. 26. If Dependencies Aren’t Injected ... var LegacySomething = function() { this.doSomething = function() { var dependency = new Dependency(); dependency.method(); }; };Thursday, 19 July 12
  27. 27. 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
  28. 28. Continuous Integration • Ruby Gem • Maven • Node.js • Rhino (Java)Thursday, 19 July 12
  29. 29. Ruby Gem require jasmine load jasmine/tasks/jasmine.rake > rake jasmine:ci https://github.com/pivotal/jasmine-gemThursday, 19 July 12
  30. 30. Maven > mvn clean test http://searls.github.com/jasmine-maven-plugin/Thursday, 19 July 12
  31. 31. Node.js > jasmine-node specs/ https://github.com/mhevery/jasmine-nodeThursday, 19 July 12
  32. 32. 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
  33. 33. 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
  34. 34. 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
  35. 35. 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
  36. 36. 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
  37. 37. 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
  38. 38. Thursday, 19 July 12

×