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

E6c6e133e74c3b83f04d2861deaa1c20?s=128

Justin Searls

October 02, 2013
Tweet

Transcript

  1. # Javascript Testing Tactics [Justin Searls](http://twitter.com/searls)

  2. # How my JavaScript Tests differ from the README. [Justin

    Searls](http://twitter.com/searls)
  3. ## 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…
  4. ## syntax

  5. ## syntax ### What I don't do

  6. ## syntax ### What I don't do * use Jasmine's

    (RSpec-like) DSL
  7. describe("Math", function(){ });

  8. describe("Math", function(){ var subject, result; });

  9. describe("Math", function(){ var subject, result; beforeEach(function(){ }); });

  10. describe("Math", function(){ var subject, result; beforeEach(function(){ subject = new Math();

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

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

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

    }); describe("#add", function(){ beforeEach(function(){ result = subject.add(4,5); }); }); });
  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(){ }); }); });
  15. 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); }); }); });
  16. ## syntax ### What I don't do * use Jasmine's

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

  18. ## syntax ### What's the problem? * Jasmine DSL has

    to be learned
  19. describe('thing', function(){}); beforeEach(function(){}); it('does stuff', function(){}); afterEach(function(){}); ... expect(true).toBeTruthy() this.addMatchers({})

    jasmine.createSpy()
  20. ## syntax ### What's the problem? * Jasmine DSL has

    to be learned * idiomatic usage is non-obvious
  21. it('does many things', function(){ });

  22. it('does many things', function(){ var subject = new Thing(); });

  23. it('does many things', function(){ var subject = new Thing(); var

    result = subject.doStuff(); });
  24. it('does many things', function(){ var subject = new Thing(); var

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

    result = subject.doStuff(); expect(result.success).toBe(true); expect(result.message).toBe("yay!"); });
  26. var subject, result;

  27. var subject, result; beforeEach(function(){ })

  28. var subject, result; beforeEach(function(){ subject = new Thing(); })

  29. var subject, result; beforeEach(function(){ subject = new Thing(); result =

    subject.doStuff(); })
  30. var subject, result; beforeEach(function(){ subject = new Thing(); result =

    subject.doStuff(); }) it('succeeds', function(){ expect(result.success).toBe(true); });
  31. 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!"); });
  32. ## syntax ### What's the problem? * Jasmine DSL has

    to be learned * idiomatic usage is non-obvious * produces distracting, verbose code
  33. ... expect(spec).toFinallyEnd(); }); }); }); }); }); });

  34. ## syntax ### What's the problem? * Jasmine DSL has

    to be learned * idiomatic usage is non-obvious * produces distracting, verbose code * dat crying mustache emoticon });
  35. ## syntax ### What I do

  36. ## syntax ### What I do * write specs in

    CoffeeScript
  37. 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); }); }); });
  38. describe "Math", -> beforeEach -> @subject = new Math() describe

    "#add", -> beforeEach -> @result = @subject.add(4,5) it "adds", -> expect(@result).toEqual(9)
  39. CoffeeScript basics*

  40. CoffeeScript basics* *Fear not, it's just JS.

  41. var add = function(a,b) { return a + b; };

  42. add = (a,b) -> a + b

  43. this.save();

  44. @save()

  45. var self = this; save(function(){ self.display("Yay!"); });

  46. save => @display("Yay!")

  47. ## syntax ### What I do * write specs in

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

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

    "#add", -> When -> @result = @subject.add(4,5) Then -> @result == 9
  50. 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); }); }); });
  51. describe "Math", -> Given -> @subject = new Math() describe

    "#add", -> When -> @result = @subject.add(4,5) Then -> @result == 9
  52. ## test runner

  53. ## test runner ### What I don't do

  54. ## test runner ### What I don't do * default

    plain HTML test runner
  55. ## test runner ### What I don't do * default

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

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

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

  59. ## test runner ### What's the problem? * feedback isn't

    fast enough
  60. ## test runner ### What's the problem? * feedback isn't

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

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

  63. ## test runner ### What I do * use Testem

    as my runner
  64. ## test runner ### What I do * use Testem

    as my runner * use Lineman to build my code
  65. ## 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
  66. ## test runner **DEMO**

  67. ## ajax & ui events

  68. ### What I don't do ## ajax & ui events

  69. ### What I don't do * start a fake server

    that can stub & verify AJAX calls ## ajax & ui events
  70. ### 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
  71. ### 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
  72. ### What's the problem? ## ajax & ui events

  73. ### What's the problem? * too integrated for unit tests

    ## ajax & ui events
  74. ### What's the problem? * too integrated for unit tests

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

    * "only mock what you own" * test pain isn't actionable ## ajax & ui events
  76. ### 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
  77. ### 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
  78. ### What I do ## ajax & ui events

  79. ### What I do * wrap XHR lib in object

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

    I own * let its API grow with my needs ## ajax & ui events
  81. ### 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
  82. ### 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
  83. **DEMO** ## ajax & ui events

  84. ## asynchronous code

  85. ## asynchronous code ### What I don't do

  86. ## asynchronous code ### What I don't do * write

    async tests for async code
  87. ## asynchronous code ### What's the problem?

  88. ## asynchronous code ### What's the problem? * test yields

    execution control
  89. ## asynchronous code ### What's the problem? * test yields

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

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

    execution control * lots of noise in test setup * speed/timeout concerns * hard to debug race conditions
  92. ## 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
  93. ## asynchronous code ### What I do

  94. ## asynchronous code ### What I do * only write

    async APIs when useful
  95. ## asynchronous code ### What I do * only write

    async APIs when useful * extract callbacks out of app code and into decorators and mixins
  96. ## 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
  97. ## 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
  98. ## the dom

  99. ## the dom ### What I don't do

  100. ## the dom ### What I don't do * say

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

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

    DOM interactions
  103. ## the dom ### What's the problem? #### not testing

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

    DOM interactions * most JS on the web is UI code * low coverage limits suite's value
  105. ## 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
  106. ## 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
  107. ## the dom ### What's the problem? #### using HTML

    fixture files
  108. ## the dom ### What's the problem? #### using HTML

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

    fixture files * large input -> larger everything * tests should push for small units
  110. ## 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_"
  111. ## the dom ### What I do

  112. ## the dom ### What I do * treat the

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

    DOM like a 3rd-party dependency—minimizing app's exposure * affix HTML with jasmine-fixture
  114. ## 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
  115. ## the dom **DEMO**

  116. ## 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!
  117. ## 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:justin@testdouble.com)
  118. My name is Justin Searls Please tweet me @searls &

    Say hello@testdouble.com