Testing Client-side Code with Jasmine and CoffeeScript

  • 3,584 views
Uploaded on

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

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

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
3,584
On Slideshare
0
From Embeds
0
Number of Embeds
5

Actions

Shares
Downloads
31
Comments
0
Likes
3

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
  • \n
  • \n
  • Why should we test client side code?\n
  • We have a web page that has images on the page. We want to think “mobile first” so we design for the small screen, using small images. We know we’ll have large screens looking at our pages so we’ll want to display large images too. So what we do is load the small images by default, and then load the large images using JavaScript. We place the sources to the large images in the custom data attributes of the images\n
  • Here’s what the JavaScript for this might look like. We find all the image elements on the page with jQuery, then iterate over them and replace the attributes.\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • So that’s all well and good for when you know what you’re doing, but what do you do when you are doing something new that you’ve never done before and don’t know how to test?\n
  • You do a spike. You write some code and work out the details.\n
  • See, the first law of testing says you may not write PRODUCTION code. So you should do spikes. You should experiment and get to know your problem domain. Then you should throw it all away and rewrite it test-driven.\n
  • In order to write JavaScript code that’s testable, we need to stop writing nasty messes of jQuery code thrown about all over our page. We need to start thinking about objects.\n
  • One of the keys to writing testable code is to start treating JavaScript as more than just a scripting language. It’s an object-oriented language and it provides many of the capabilities that we would take advantage of in our other languages. The biggest of those is encapsulation.\n
  • There are lots of ways to define objects in JavaScript. There are some great books out there on the subject, but one of the most simple ways is to simply use an object literal. \n
  • Here’s what the JavaScript for this might look like. We find all the image elements on the page with jQuery, then iterate over them and replace the attributes.\n
  • Remember this code? So this is just jQuery code without any encapsulation whatsoever. It would be much nicer if we could do something like this:\n
  • We can take all that messy code, put it in an object, and simply call that. Just by doing that alone, we’ve now made it a lot easier to test. \n
  • But how do we actually do TDD in JavaScript? \n
  • \n
  • Jasmine is my testing framework of choice. It’s simple, easy to read, has great support, lots of nice plugins, and can be set to run in the browser or in an automated fashion.\n
  • Jasmine tests are pretty simple.\n
  • Describe describes a behavior we’re testing.\n
  • It() is an actual test declaration\n
  • Expect() runs some sort of check.\n
  • Using Jasmine, we simply write a test case using Jasmine’s syntax that invokes our ImageReplacer object\n
  • then do an expects statement that checks to see if something worked. Of course it’s a little more complicated than that.\n
  • One other thing I’ve found incredibly useful for developing and testing this kind of code is CoffeeScript. \n
  • With CoffeeScript’s syntax we can change this....\n
  • into this. \n
  • \n
  • \n
  • \n
  • \n
  • Variable declarations are all done by omitting the var keyword. CoffeeScript automatically adds this so we don’t accidentally get a variable in the wrong scope.\n
  • \n
  • The skinny arrow in CoffeeScript basically means the function declaration in JavaScript. It’s less typing and less parentheses and curly braces to match. \n
  • That matters a lot when doing things like this....\n
  • because we can make them look like this. It becomes much easier to read when doing nested functions.\n
  • When CoffeeScript is compiled to JavaScript, the JavaScript is all wrapped in an anonymous function which makes it much harder to accidentally pollute the global namespace.\n
  • \n
  • Cake is a build language that comes with CoffeeScript when you install it with Node.js’s npm package manager. With cake we can set up a build system to automatically compile CoffeeScript files to JavaScript as we work.\n
  • With this simple Cakefile in our project, we can easily start working with CoffeeScript.\n
  • \n
  • We can download and unzip the Jasmine Standalone download. This gives us everything we need to start doing some BDD work.\n
  • \n
  • \n
  • \n
  • Heavy use of spies and mock objects can make your test suite run fast and keep it decoupled. For example:\n
  • The secret is to try to decouple things as much as possible.\n
  • I have a Twitter object\n
  • That Twitter object is going to do a jQuery AJAX call. I can spy on jQUery’s ajax function.\n
  • When I call my Twitter object’s search method, it won’t actually call out to Twitter. But I can pretend it did and I can then check to ensure the URL my object constructed was correctly built from the search term. \n
  • See? Here’s the assertion. When I created the spy I could have actually fed it a response that I could then parse out. So I can write these tests without ever hitting the server.\n
  • \n
  • \n
  • \n

Transcript

  • 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. about me I write books I build things I teach peopleBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 3. Why should we test client-side code?Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. 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. 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. But client-side testing can be hard!Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. “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. “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. “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. But what if I don’t know how to test the feature?Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 13. Spike!Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. Writing Testable CodeBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 16. Use objects! encapsulation!Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 17. object literalsBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. 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. Isn’t this cleaner? $(function(){ ImageReplacer.replaceAll(); })Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 21. But how do we test?Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 22. Lots of options QUnit Mocha Jasmine ... lots moreBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 23. JasmineBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. 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. 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. 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. 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. 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. CoffeeScriptBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. 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. CoffeeScript helps us write better JavaScript.Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 34. CoffeeScript uses significant whitespace for scope.Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 35. CoffeeScript borrows from Python and Ruby syntax.Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 36. CoffeeScript compiles to regular, plain old JavaScript.Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. 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. -> means function(){}Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. 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. CoffeeScript wraps everything in (function(){ ... })()Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. cakeBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. Project Structure Project/ src/ ImageReplacer.coffee spec/ ImageReplacer_spec.coffee CakefileBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 47. Jasmine-Standalone https://github.com/pivotal/jasmine/ downloadsBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. 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. DemoBrian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 51. Async code? Use spies!Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 52. Decouple things!Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com
  • 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. 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. 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. 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. 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. 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. http://spkr8.com/t/16851Brian P. Hogan http://spkr8.com/t/16851twitter: @bphoganwww.bphogan.com