Your SlideShare is downloading. ×
Testing javascriptwithjasmine ddd-sydney
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

Testing javascriptwithjasmine ddd-sydney

1,174
views

Published on

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

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,174
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
9
Comments
0
Likes
2
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Testing JavaScript LIKE A BOSS Jo Cranford @jocranfordThursday, 19 July 12
  • 2. Y U NO JAVASCRIPT TESTS?Thursday, 19 July 12
  • 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
  • 7. Basic ShizzleThursday, 19 July 12
  • 8. Jasmine In Your Project • Ruby Gem • Maven • Node.js • StandaloneThursday, 19 July 12
  • 9. Ruby Gem > jasmine init > rake jasmine https://github.com/pivotal/jasmine-gemThursday, 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
  • 11. Node.js > npm install jasmine-node -g > jasmine-node specs/ https://github.com/mhevery/jasmine-nodeThursday, 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/downloadsThursday, 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
  • 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. .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
  • 19. Is JavaScript Ever Really That Simple?Thursday, 19 July 12
  • 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. Approaches To Testing Asynchronous CodeThursday, 19 July 12
  • 22. Let’s Load Some JSON [ { "firstName": "Jo", "lastName": "Cranford", "company": "Atlassian" }, { "firstName": "Rachel", "lastName": "Laycock", "company": "ThoughtWorks" } ]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
  • 25. Well ... Not So Much.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
  • 27. A Little Detour ...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 aboutThursday, 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 viewThursday, 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-jqueryThursday, 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 intentionsThursday, 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
  • 43. Continuous Integration • Ruby Gem • Maven • Node.js • Rhino (Java)Thursday, 19 July 12
  • 44. Ruby Gem require jasmine load jasmine/tasks/jasmine.rake > rake jasmine:ci https://github.com/pivotal/jasmine-gemThursday, 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-nodeThursday, 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.htmlThursday, 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
  • 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. 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 maintainThursday, 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 browsersThursday, 19 July 12
  • 55. With thanks to our sponsorsThursday, 19 July 12
  • 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. QuestionsThursday, 19 July 12