Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

JavaScript Testing Tactics

JavaScript Testing Tactics

More details here: http://blog.testdouble.com/posts/2013-10-03-javascript-testing-tactics.html

This is a presentation that describes where (and why!) I've arrived after several years of practicing JavaScript testing. Specifically, I cover the obvious or the standard approach and express how my practice currently differs

Justin Searls

October 02, 2013
Tweet

More Decks by Justin Searls

Other Decks in Programming

Transcript

  1. ## background * make front-end dev _delightful_ * create beautiful

    test techniques * discover when testing is valuable * typically use jasmine * maintain lots of supporting tools, like Lineman.js, jasmine-given, jasmine-fixture, jasmine-stealth, jasmine-before-all, grunt-jasmine…
  2. describe("Math", function(){ var subject, result; beforeEach(function(){ subject = new Math();

    }); describe("#add", function(){ beforeEach(function(){ }); }); });
  3. describe("Math", function(){ var subject, result; beforeEach(function(){ subject = new Math();

    }); describe("#add", function(){ beforeEach(function(){ result = subject.add(4,5); }); }); });
  4. describe("Math", function(){ var subject, result; beforeEach(function(){ subject = new Math();

    }); describe("#add", function(){ beforeEach(function(){ result = subject.add(4,5); }); it("adds", function(){ }); }); });
  5. describe("Math", function(){ var subject, result; beforeEach(function(){ subject = new Math();

    }); describe("#add", function(){ beforeEach(function(){ result = subject.add(4,5); }); it("adds", function(){ expect(result).toEqual(9); }); }); });
  6. ## syntax ### What I don't do * use Jasmine's

    (RSpec-like) DSL * write my specs with JavaScript
  7. ## syntax ### What's the problem? * Jasmine DSL has

    to be learned * idiomatic usage is non-obvious
  8. it('does many things', function(){ var subject = new Thing(); var

    result = subject.doStuff(); expect(result.success).toBe(true); });
  9. it('does many things', function(){ var subject = new Thing(); var

    result = subject.doStuff(); expect(result.success).toBe(true); expect(result.message).toBe("yay!"); });
  10. var subject, result; beforeEach(function(){ subject = new Thing(); result =

    subject.doStuff(); }) it('succeeds', function(){ expect(result.success).toBe(true); });
  11. var subject, result; beforeEach(function(){ subject = new Thing(); result =

    subject.doStuff(); }) it('succeeds', function(){ expect(result.success).toBe(true); }); it('exclaims triumph!', function({ expect(result.message).toBe("yay!"); });
  12. ## syntax ### What's the problem? * Jasmine DSL has

    to be learned * idiomatic usage is non-obvious * produces distracting, verbose code
  13. ## syntax ### What's the problem? * Jasmine DSL has

    to be learned * idiomatic usage is non-obvious * produces distracting, verbose code * dat crying mustache emoticon });
  14. describe("Math", function(){ var subject, result; beforeEach(function(){ subject = new Math();

    }); describe("#add", function(){ beforeEach(function(){ result = subject.add(4,5); }); it("adds", function(){ expect(result).toEqual(9); }); }); });
  15. describe "Math", -> beforeEach -> @subject = new Math() describe

    "#add", -> beforeEach -> @result = @subject.add(4,5) it "adds", -> expect(@result).toEqual(9)
  16. ## syntax ### What I do * write specs in

    CoffeeScript * use the jasmine-given DSL
  17. describe "Math", -> beforeEach -> @subject = new Math() describe

    "#add", -> beforeEach -> @result = @subject.add(4,5) it "adds", -> expect(@result).toEqual(9)
  18. describe "Math", -> Given -> @subject = new Math() describe

    "#add", -> When -> @result = @subject.add(4,5) Then -> @result == 9
  19. describe("Math", function(){ var subject, result; beforeEach(function(){ subject = new Math();

    }); describe("#add", function(){ beforeEach(function(){ result = subject.add(4,5); }); it("adds", function(){ expect(result).toEqual(9); }); }); });
  20. describe "Math", -> Given -> @subject = new Math() describe

    "#add", -> When -> @result = @subject.add(4,5) Then -> @result == 9
  21. ## test runner ### What I don't do * default

    plain HTML test runner * jasmine-rails
  22. ## test runner ### What I don't do * default

    plain HTML test runner * jasmine-rails * jasmine-maven-plugin
  23. ## test runner ### What I don't do * default

    plain HTML test runner * jasmine-rails * jasmine-maven-plugin * any server-side-dependent plugin
  24. ## test runner ### What's the problem? * feedback isn't

    fast enough * build tools treat JS as 2nd-class
  25. ## test runner ### What's the problem? * feedback isn't

    fast enough * build tools treat JS as 2nd-class * friction of server-side coupling
  26. ## test runner ### What I do * use Testem

    as my runner * use Lineman to build my code
  27. ## test runner ### What I do * use Testem

    as my runner * use Lineman to build my code * run all my tests in under a second on every single file change
  28. ### What I don't do * start a fake server

    that can stub & verify AJAX calls ## ajax & ui events
  29. ### What I don't do * start a fake server

    that can stub & verify AJAX calls * monkey-patch the browser's XHR facilities ## ajax & ui events
  30. ### What I don't do * start a fake server

    that can stub & verify AJAX calls * monkey-patch the browser's XHR facilities * invoke code by triggering UI events ## ajax & ui events
  31. ### What's the problem? * too integrated for unit tests

    * "only mock what you own" ## ajax & ui events
  32. ### What's the problem? * too integrated for unit tests

    * "only mock what you own" * test pain isn't actionable ## ajax & ui events
  33. ### What's the problem? * too integrated for unit tests

    * "only mock what you own" * test pain isn't actionable * raises concerns better handled by integrated tests (e.g. large stubs) ## ajax & ui events
  34. ### What's the problem? * too integrated for unit tests

    * "only mock what you own" * test pain isn't actionable * raises concerns better handled by integrated tests (e.g. large stubs) * doesn't help improve my API ## ajax & ui events
  35. ### What I do * wrap XHR lib in object

    I own ## ajax & ui events
  36. ### What I do * wrap XHR lib in object

    I own * let its API grow with my needs ## ajax & ui events
  37. ### What I do * wrap XHR lib in object

    I own * let its API grow with my needs * mock it away in my unit tests ## ajax & ui events
  38. ### What I do * wrap XHR lib in object

    I own * let its API grow with my needs * mock it away in my unit tests * only integration test the wrapper ## ajax & ui events
  39. ## asynchronous code ### What's the problem? * test yields

    execution control * lots of noise in test setup
  40. ## asynchronous code ### What's the problem? * test yields

    execution control * lots of noise in test setup * speed/timeout concerns
  41. ## asynchronous code ### What's the problem? * test yields

    execution control * lots of noise in test setup * speed/timeout concerns * hard to debug race conditions
  42. ## asynchronous code ### What's the problem? * test yields

    execution control * lots of noise in test setup * speed/timeout concerns * hard to debug race conditions * test pain isn't actionable
  43. ## asynchronous code ### What I do * only write

    async APIs when useful * extract callbacks out of app code and into decorators and mixins
  44. ## asynchronous code ### What I do * only write

    async APIs when useful * extract callbacks out of app code and into decorators and mixins * take advantage of promises
  45. ## asynchronous code ### What I do * only write

    async APIs when useful * extract callbacks out of app code and into decorators and mixins * take advantage of promises * use jasmine-stealth to capture & discretely test callback functions
  46. ## the dom ### What I don't do * say

    "(╯°□°ʣ╯ớ ┻━<table/>━┻" and skip writing tests against the DOM
  47. ## the dom ### What I don't do * say

    "(╯°□°ʣ╯ớ ┻━<table/>━┻" and skip writing tests against the DOM * use HTML fixture files
  48. ## the dom ### What's the problem? #### not testing

    DOM interactions * most JS on the web is UI code
  49. ## the dom ### What's the problem? #### not testing

    DOM interactions * most JS on the web is UI code * low coverage limits suite's value
  50. ## the dom ### What's the problem? #### not testing

    DOM interactions * most JS on the web is UI code * low coverage limits suite's value * you'll write more DOM-heavy code via the path of least resistance
  51. ## the dom ### What's the problem? #### not testing

    DOM interactions * most JS on the web is UI code * low coverage limits suite's value * you'll write more DOM-heavy code via the path of least resistance * hampers true outside-in TDD
  52. ## the dom ### What's the problem? #### using HTML

    fixture files * large input -> larger everything
  53. ## the dom ### What's the problem? #### using HTML

    fixture files * large input -> larger everything * tests should push for small units
  54. ## the dom ### What's the problem? #### using HTML

    fixture files * large input -> larger everything * tests should push for small units * sharing fixtures leads to a "_Tragedy of the Commons_"
  55. ## the dom ### What I do * treat the

    DOM like a 3rd-party dependency—minimizing app's exposure
  56. ## the dom ### What I do * treat the

    DOM like a 3rd-party dependency—minimizing app's exposure * affix HTML with jasmine-fixture
  57. ## the dom ### What I do * treat the

    DOM like a 3rd-party dependency—minimizing app's exposure * affix HTML with jasmine-fixture * arrive at single-purpose functions
  58. ## in summary > Practicing TDD in JS for years

    taught me how to write better code. When TDD feels rote, it means I learned something! So then I use it less. Give it a try! I'll help!
  59. ## Lineman You can have all these helpers pre- installed

    and ready to go for you with Lineman! * [Lineman](http://linemanjs.com) * [Install](http://lineman-install.herokuapp.com) * [Help](mailto:[email protected])