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 Slide

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

    View 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 Slide

  4. ## syntax

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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(){
    });
    });
    });

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  26. var subject, result;

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  35. ## syntax
    ### What I do

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  39. CoffeeScript basics*

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  43. this.save();

    View Slide

  44. @save()

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. ## test runner

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  62. ## test runner
    ### What I do

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  66. ## test runner
    **DEMO**

    View Slide

  67. ## ajax & ui events

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  84. ## asynchronous code

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  93. ## asynchronous code
    ### What I do

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  98. ## the dom

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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_"

    View Slide

  111. ## the dom
    ### What I do

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  115. ## the dom
    **DEMO**

    View Slide

  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!

    View Slide

  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:[email protected])

    View Slide

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

    View Slide