Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Testing Client-side Code with Jasmine and CoffeeScript

5,387 views

Published on

My Twin Cities Code Camp talk covers how to develop client-side code test-first with Jasmine and CoffeeScript.

  • Be the first to comment

Testing Client-side Code with Jasmine and CoffeeScript

  1. 1. Testing Client-Side Code with CoffeeScript and JasmineBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com Rate this talk at http://spkr8.com/t/8682
  2. 2. about me I write books I build things I teach peopleBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  3. 3. Why should we test client-side code?Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  4. 4. simple code is not simple. <img src="picture1.png" data-large-image="picture1-large.png"> <img src="picture2.png" data-large-image="picture2-large.png"> <img src="picture3.png" data-large-image="picture3-large.png">Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  5. 5. simple code is not simple. $(function(){ if($(window).width() > 480){ var images = $("img"); images.each(function(index){ $(this).attr("src", $(this).attr("data-large-image")); }); } })Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  6. 6. Lots of logic Detecting the screen size testing that each image got changed checking that it only happens on large screensBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  7. 7. But client-side testing can be hard!Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  8. 8. Challenges The DOM Tightly-coupled code Callbacks We don’t know how to testBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  9. 9. “Laws of Testing” You may not write production code until you have written a failing unit test.Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  10. 10. “Laws of Testing” You may not write more of a unit test than is sufficient to fail, and not compiling is failingBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  11. 11. “Laws of Testing” You may not write more production code than is sufficient to pass the currently failing testBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  12. 12. But what if I don’t know how to test the feature?Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  13. 13. Spike!Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  14. 14. You may not write production code until you have written a failing unit test.Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  15. 15. Writing Testable CodeBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  16. 16. Use objects! encapsulation!Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  17. 17. object literalsBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  18. 18. object literal var MyObject = { sayHello: function(){ alert("Hello!"); }, sayGoodbye: function(){ alert("Goodbye"); } }; MyObject.sayHello(); MyObject.sayGoodbye();Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  19. 19. Remember this? $(function(){ if($(window).width() > 480){ var images = $("img"); images.each(function(index){ $(this).attr("src", $(this).attr("data-large-image")); }); } })Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  20. 20. Isn’t this cleaner? $(function(){ ImageReplacer.replaceAll(); })Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  21. 21. But how do we test?Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  22. 22. Lots of options QUnit Mocha Jasmine ... lots moreBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  23. 23. JasmineBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  24. 24. A Jasmine Test describe("JavaScripts Math works!", function() { it("adds numbers properly", function() { var result = 5 + 5; expect(result).toEqual(10); }); });Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  25. 25. Describe describe("JavaScripts Math works!", function() { it("adds numbers properly", function() { var result = 5 + 5; expect(result).toEqual(10); }); });Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  26. 26. it() describe("JavaScripts Math works!", function() { it("adds numbers properly", function() { var result = 5 + 5; expect(result).toEqual(10); }); });Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  27. 27. expect() describe("JavaScripts Math works!", function() { it("adds numbers properly", function() { var result = 5 + 5; expect(result).toEqual(10); }); });Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  28. 28. Jasmine Test describe("The ImageReplacer object", function() { it("replaces picture1 with picture1-large", function() { ImageReplacer.replaceAll(); expect($("#picture1").attr("src")).toEqual("picture1-large.jpg"); }); });Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  29. 29. Jasmine Test describe("The ImageReplacer object", function() { it("replaces picture1 with picture1-large", function() { ImageReplacer.replaceAll(); expect($("#picture1").attr("src")).toEqual("picture1-large.jpg"); }); });Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  30. 30. CoffeeScriptBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  31. 31. Jasmine Test describe("The ImageReplacer object", function() { it("replaces picture1 with picture1-large", function() { ImageReplacer.replaceAll(); expect($("#picture1").attr("src")).toEqual("picture1-large.jpg"); }); });Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  32. 32. Into this. describe "JavaScripts Math works!", -> it "adds numbers properly", -> result = 5 + 5 expect(result).toEqual 10Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  33. 33. CoffeeScript helps us write better JavaScript.Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  34. 34. CoffeeScript uses significant whitespace for scope.Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  35. 35. CoffeeScript borrows from Python and Ruby syntax.Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  36. 36. CoffeeScript compiles to regular, plain old JavaScript.Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  37. 37. Declaring variables and objects var box, colors, firstName, lastName; firstName = "Homer" firstName = "Homer"; lastName = "Simpson" lastName = "Simpson"; colors = ["Green", "Blue", "Red"] colors = ["Green", "Blue", "Red"]; box = box = { height: 40 height: 40, width: 60 width: 60, color: red color: red };Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  38. 38. CoffeeScript uses function expressions. hello (name) -> alert "Hello #{name}" var hello = function(name){ alert("Hello " + name + "!"); }Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  39. 39. -> means function(){}Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  40. 40. Not bad... describe("The ImageReplacer object", function() { it("replaces picture1 with picture1-large", function() { ImageReplacer.replaceAll(); expect($("#picture1").attr("src")).toEqual("picture1-large.jpg"); }); });Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  41. 41. Much clearer. describe "JavaScripts Math works!", -> it "adds numbers properly", -> result = 5 + 5 expect(result).toEqual 10Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  42. 42. CoffeeScript wraps everything in (function(){ ... })()Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  43. 43. CoffeeScript Advantages Everything wrapped in an anonymous function Less noise Helps enforce good JS Style Makes tests easier to readBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  44. 44. cakeBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  45. 45. {spawn, exec} = require child_process task watch, continually build with --watch, -> src = spawn coffee, [-cw, src] src.stdout.on data, (data) -> console.log data.toString().trim() spec = spawn coffee, [-cw, spec] spec.stdout.on data, (data) -> console.log data.toString().trim()Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  46. 46. Project Structure Project/ src/ ImageReplacer.coffee spec/ ImageReplacer_spec.coffee CakefileBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  47. 47. Jasmine-Standalone https://github.com/pivotal/jasmine/ downloadsBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  48. 48. Demo Write our ImageReplacer with CoffeeScript, test-first. Replace an image Replace all images Ensure mobile onlyBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  49. 49. Requirements Mobile sites (< 900 px wide) load small images Other sizes (> 900px wide) load larger images Only images with large images should changeBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  50. 50. DemoBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  51. 51. Async code? Use spies!Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  52. 52. Decouple things!Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  53. 53. describe "Searching Twitter", -> it "creates an AJAX request to Twitter with the keyword JavaScript", -> expectedURL = "http://search.twitter.com/search.json?q=javascript" twitter = new Twitter() spy = spyOn($, "ajax") twitter.search "javascript" generatedURL = spy.mostRecentCall.args[0].url expect(generatedURL).toEqual expectedURLBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  54. 54. describe "Searching Twitter", -> it "creates an AJAX request to Twitter with the keyword JavaScript", -> expectedURL = "http://search.twitter.com/search.json?q=javascript" twitter = new Twitter() spy = spyOn($, "ajax") twitter.search "javascript" generatedURL = spy.mostRecentCall.args[0].url expect(generatedURL).toEqual expectedURLBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  55. 55. describe "Searching Twitter", -> it "creates an AJAX request to Twitter with the keyword JavaScript", -> expectedURL = "http://search.twitter.com/search.json?q=javascript" twitter = new Twitter() spy = spyOn($, "ajax") twitter.search "javascript" generatedURL = spy.mostRecentCall.args[0].url expect(generatedURL).toEqual expectedURLBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  56. 56. describe "Searching Twitter", -> it "creates an AJAX request to Twitter with the keyword JavaScript", -> expectedURL = "http://search.twitter.com/search.json?q=javascript" twitter = new Twitter() spy = spyOn($, "ajax") twitter.search "javascript" generatedURL = spy.mostRecentCall.args[0].url expect(generatedURL).toEqual expectedURLBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  57. 57. What we learned Putting things in objects makes them more testable CoffeeScript and Jasmine make tests easier to read and write Client-side code can be testable just like server-side codeBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  58. 58. What next? Explore Phantom.js for headless testing Integrate client-side testing into a continuous integration server Investigate other ways to create objectsBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  59. 59. http://spkr8.com/t/16851Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com

×