Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. # Javascript Testing Tactics
    [Justin Searls](http://twitter.com/searls)

    View full-size slide

  2. # How my JavaScript Tests
    differ from the README.
    [Justin Searls](http://twitter.com/searls)

    View full-size slide

  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…

    View full-size slide

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

    View full-size slide

  5. ## syntax
    ### What I don't do
    * use Jasmine's (RSpec-like) DSL

    View full-size slide

  6. describe("Math", function(){
    });

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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);
    });
    });
    });

    View full-size slide

  15. ## syntax
    ### What I don't do
    * use Jasmine's (RSpec-like) DSL
    * write my specs with JavaScript

    View full-size slide

  16. ## syntax
    ### What's the problem?

    View full-size slide

  17. ## syntax
    ### What's the problem?
    * Jasmine DSL has to be learned

    View full-size slide

  18. describe('thing', function(){});
    beforeEach(function(){});
    it('does stuff', function(){});
    afterEach(function(){});
    ...
    expect(true).toBeTruthy()
    this.addMatchers({})
    jasmine.createSpy()

    View full-size slide

  19. ## syntax
    ### What's the problem?
    * Jasmine DSL has to be learned
    * idiomatic usage is non-obvious

    View full-size slide

  20. it('does many things', function(){
    });

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  25. var subject, result;

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  30. 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!");
    });

    View full-size slide

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

    View full-size slide

  32. ...
    expect(spec).toFinallyEnd();
    });
    });
    });
    });
    });
    });

    View full-size slide

  33. ## syntax
    ### What's the problem?
    * Jasmine DSL has to be learned
    * idiomatic usage is non-obvious
    * produces distracting, verbose code
    * dat crying mustache emoticon });

    View full-size slide

  34. ## syntax
    ### What I do

    View full-size slide

  35. ## syntax
    ### What I do
    * write specs in CoffeeScript

    View full-size slide

  36. 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);
    });
    });
    });

    View full-size slide

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

    View full-size slide

  38. CoffeeScript basics*

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  42. this.save();

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  45. ## syntax
    ### What I do
    * write specs in CoffeeScript
    * use the jasmine-given DSL

    View full-size slide

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

    View full-size slide

  47. describe "Math", ->
    Given -> @subject = new Math()
    describe "#add", ->
    When -> @result = @subject.add(4,5)
    Then -> @result == 9

    View full-size slide

  48. 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);
    });
    });
    });

    View full-size slide

  49. describe "Math", ->
    Given -> @subject = new Math()
    describe "#add", ->
    When -> @result = @subject.add(4,5)
    Then -> @result == 9

    View full-size slide

  50. ## test runner

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  55. ## test runner
    ### What I don't do
    * default plain HTML test runner
    * jasmine-rails
    * jasmine-maven-plugin
    * any server-side-dependent plugin

    View full-size slide

  56. ## test runner
    ### What's the problem?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  59. ## test runner
    ### What's the problem?
    * feedback isn't fast enough
    * build tools treat JS as 2nd-class
    * friction of server-side coupling

    View full-size slide

  60. ## test runner
    ### What I do

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  63. ## 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

    View full-size slide

  64. ## test runner
    **DEMO**

    View full-size slide

  65. ## ajax & ui events

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  68. ### 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

    View full-size slide

  69. ### 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

    View full-size slide

  70. ### What's the problem?
    ## ajax & ui events

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  74. ### 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

    View full-size slide

  75. ### 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

    View full-size slide

  76. ### What I do
    ## ajax & ui events

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  79. ### 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

    View full-size slide

  80. ### 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

    View full-size slide

  81. **DEMO**
    ## ajax & ui events

    View full-size slide

  82. ## asynchronous code

    View full-size slide

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

    View full-size slide

  84. ## asynchronous code
    ### What I don't do
    * write async tests for async code

    View full-size slide

  85. ## asynchronous code
    ### What's the problem?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  90. ## 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

    View full-size slide

  91. ## asynchronous code
    ### What I do

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  94. ## 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

    View full-size slide

  95. ## 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  98. ## the dom
    ### What I don't do
    * say "(╯°□°ʣ╯ớ ┻━━┻" and
    skip writing tests against the DOM
    * use HTML fixture files

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  102. ## 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

    View full-size slide

  103. ## 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  107. ## 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_"

    View full-size slide

  108. ## the dom
    ### What I do

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  111. ## 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

    View full-size slide

  112. ## the dom
    **DEMO**

    View full-size slide

  113. ## 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!

    View full-size slide

  114. ## 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])

    View full-size slide

  115. My name is Justin Searls
    Please tweet me @searls &
    Say [email protected]

    View full-size slide