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

Writing Testable JavaScript

Writing Testable JavaScript

Slides from my 2012 Full Frontal presentation.

0177cdce6af15e10db15b6bf5dc4e0b0?s=128

Rebecca Murphey

November 09, 2012
Tweet

Transcript

  1. Writing Testable JavaScript Rebecca Murphey • Full Frontal 2012 ©

     brianUphoto  http://www.flickr.com/photos/snype451/5752753663/in/photostream/ Friday, November 9, 12
  2. rmurphey.com • @rmurphey • bocoup.com Friday, November 9, 12

  3. Writing Testable JavaScript Rebecca Murphey • Full Frontal 2012 ©

     brianUphoto  http://www.flickr.com/photos/snype451/5752753663/in/photostream/ Friday, November 9, 12
  4. Friday, November 9, 12

  5. Friday, November 9, 12

  6. Friday, November 9, 12

  7. you will design your code you will test your code

    you will refactor your code other people will use your code there will be bugs facts of life Friday, November 9, 12
  8. Friday, November 9, 12

  9. var  resultsList  =  $(  '#results'  ); var  liked  =  $(

     '#liked'  ); var  pending  =  false; $(  '#searchForm'  ).on(  'submit',  function(  e  )  {    e.preventDefault();    if  (  pending  )  {  return;  }    var  form  =  $(  this  );    var  query  =  $.trim(  form.find(  'input[name="q"]'  ).val()  );    if  (  !query  )  {  return;  }    pending  =  true;    $.ajax(  '/data/search.json',  {        data  :  {  q:  query  },        dataType  :  'json',        success  :  function(  data  )  {            var  tmpl  =  _.template(                  $('#tmpl-­‐people-­‐detailed').text()              );            resultsList.html(tmpl({                  people  :  data.results              }));            pending  =  false;        }    });    $('<li>',  {        'class'  :  'pending',        html  :  'Searching  &hellip;'    }).appendTo(  resultsList.empty()  ); }); Friday, November 9, 12
  10. “do the pieces work together as expected?” integration tests Friday,

    November 9, 12
  11. def  test_no_results    fill_in('q',  :with  =>  'foobarbazbimbop')    find('.btn').click  

         assert(          page.has_selector?('#results  li.no-­‐results'),          'No  results  is  shown'      )        assert(          find('#results').has_content?('No  results  found'),          'No  results  message  is  shown'      ) end selenium (via ruby) Friday, November 9, 12
  12. Friday, November 9, 12

  13. Friday, November 9, 12

  14. “given input x, is the output y?” unit tests Friday,

    November 9, 12
  15. var  resultsList  =  $(  '#results'  ); var  liked  =  $(

     '#liked'  ); var  pending  =  false; $(  '#searchForm'  ).on(  'submit',  function(  e  )  {    e.preventDefault();    if  (  pending  )  {  return;  }    var  form  =  $(  this  );    var  query  =  $.trim(  form.find(  'input[name="q"]'  ).val()  );    if  (  !query  )  {  return;  }    pending  =  true;    $.ajax(  '/data/search.json',  {        data  :  {  q:  query  },        dataType  :  'json',        success  :  function(  data  )  {            var  tmpl  =  _.template(                  $('#tmpl-­‐people-­‐detailed').text()              );            resultsList.html(tmpl({                  people  :  data.results              }));            pending  =  false;        }    });    $('<li>',  {        'class'  :  'pending',        html  :  'Searching  &hellip;'    }).appendTo(  resultsList.empty()  ); }); Friday, November 9, 12
  16. anonymous functions, lack of structure complex, oversized functions lack of

    con gurability hidden or shared state tightly coupled difficult to test Friday, November 9, 12
  17. var  resultsList  =  $(  '#results'  ); var  liked  =  $(

     '#liked'  ); var  pending  =  false; $(  '#searchForm'  ).on(  'submit',  function(  e  )  {    e.preventDefault();    if  (  pending  )  {  return;  }    var  form  =  $(  this  );    var  query  =  $.trim(  form.find(  'input[name="q"]'  ).val()  );    if  (  !query  )  {  return;  }    pending  =  true;    $.ajax(  '/data/search.json',  {        data  :  {  q:  query  },        dataType  :  'json',        success  :  function(  data  )  {            var  tmpl  =  _.template(                  $('#tmpl-­‐people-­‐detailed').text()              );            resultsList.html(tmpl({                  people  :  data.results              }));            pending  =  false;        }    });    $('<li>',  {        'class'  :  'pending',        html  :  'Searching  &hellip;'    }).appendTo(  resultsList.empty()  ); }); var  resultsList  =  $(  '#results'  ); var  liked  =  $(  '#liked'  ); var  pending  =  false; $(  '#searchForm'  ).on(  'submit',  function(  e  )  {    //  ... }); Friday, November 9, 12
  18. var  resultsList  =  $(  '#results'  ); var  liked  =  $(

     '#liked'  ); var  pending  =  false; $(  '#searchForm'  ).on(  'submit',  function(  e  )  {    e.preventDefault();    if  (  pending  )  {  return;  }    var  form  =  $(  this  );    var  query  =  $.trim(  form.find(  'input[name="q"]'  ).val()  );    if  (  !query  )  {  return;  }    pending  =  true;    $.ajax(  '/data/search.json',  {        data  :  {  q:  query  },        dataType  :  'json',        success  :  function(  data  )  {            var  tmpl  =  _.template(                  $('#tmpl-­‐people-­‐detailed').text()              );            resultsList.html(tmpl({                  people  :  data.results              }));            pending  =  false;        }    });    $('<li>',  {        'class'  :  'pending',        html  :  'Searching  &hellip;'    }).appendTo(  resultsList.empty()  ); }); $.ajax(  '/data/search.json',  {    data  :  {  q:  query  },    dataType  :  'json',    success  :  function(  data  )  {        var  tmpl  =  _.template(              $('#tmpl-­‐people-­‐detailed').text()          );        resultsList.html(tmpl({              people  :  data.results          }));        pending  =  false;    } }); Friday, November 9, 12
  19. Friday, November 9, 12

  20. setup presentation &  interaction application state data/server   communication Friday,

    November 9, 12
  21. Friday, November 9, 12

  22. search data application state glue Friday, November 9, 12

  23. use constructors to create instances support con gurability keep methods

    simple don’t intermingle responsibilities guiding principles Friday, November 9, 12
  24. test rst. Friday, November 9, 12

  25. var  liked  =  $(  '#liked'  ); var  resultsList  =  $(

     '#results'  ); //  ... resultsList.on(  'click',  '.like',  function(e)  {    e.preventDefault();    var  name  =  $(  this  ).closest(  'li'  ).find(  'h2'  ).text();    liked.find(  '.no-­‐results'  ).remove();    $(  '<li>',  {  text:  name  }  ).appendTo(  liked  ); }); Friday, November 9, 12
  26. test('create  a  likes  view',  function()  {    var  view  =

     new  app.Views.Likes({        el:  '#likes'    });        ok(  view  ); }); Friday, November 9, 12
  27. test('add  a  liked  person',  function()  {    var  view  =

     new  app.Views.Likes({        el:  '#likes'    });        view.add(  'Brendan  Eich'  );        ok(  $('#likes').html().match('Brendan  Eich')  ); }); Friday, November 9, 12
  28. app.Views.Likes  =  (function()  {    var  Likes  =  function(  settings

     )  {        this.$el  =  $(  settings.el  );    };    Likes.prototype.add  =  function(  name  )  {        this.$el.find(  '.no-­‐results'  ).remove();        $(  '<li>',  {  html  :  name  }  ).appendTo(  this.$el  );    };    return  Likes; }()); Friday, November 9, 12
  29. $.ajax(  '/data/search.json',  {    data  :  {  q:  query  },

       dataType  :  'json',    success  :  function(  data  )  {        var  tmpl  =  _.template(  $('#tmpl-­‐people-­‐detailed').text()  );        resultsList.html(  tmpl({  people  :  data.results  })  );        pending  =  false;    } }); Friday, November 9, 12
  30. test('adding  search  results',  function()  {    var  view  =  new

     app.Views.SearchResults({        el:  '#results'    });        view.setData([        {  name:  'Rebecca',  company:  {  name:  'Bocoup'  },  email:  'rebecca@        {  name:  'Dan',  company:  {  name:  'Bocoup'  },  email:  'dan@bocoup.c    ]);        var  html  =  $(  '#results'  ).html();        ok(  html.match(  'Rebecca'  )  );    ok(  html.match(  'Dan'  )  );    equal(  $(  '#results  li'  ).length,  2  ); }); Friday, November 9, 12
  31. app.Views.SearchResults  =  (function()  {    var  SearchResults  =  function(  settings

     )  {        this.app  =  settings.app;        this.$el  =  $(  settings.el  );    };    SearchResults.prototype.set  =  function(  people  )  {        var  $el  =  this.$el.empty();        return  app.loadTemplate(  'people-­‐detailed.tmpl'  )            .done(function(  t  )  {                $el.html(  t(  {  people  :  people  }  )  );            });    };    return  SearchResults; }()); Friday, November 9, 12
  32. test('search  data  URL',  function()  {    var  search  =  new

     app.Search();    var  result  =  search.fetch(  'cat'  );    equal(  requests[0].url,  '/data/search.json?q=cat'  ); }); Friday, November 9, 12
  33. module('search  data',  {    setup  :  function()  {    

       xhr  =  sinon.useFakeXMLHttpRequest();        requests  =  [];        xhr.onCreate  =  function(  req  )  {              requests.push(  req  );          };    },    teardown  :  function()  {        xhr.restore();    } }); Friday, November 9, 12
  34. test('fetch  returns  a  promise',  function()  {    var  search  =

     new  app.Search();    var  result  =  search.fetch(  'cat'  );    ok(  result.then  ); }); Friday, November 9, 12
  35. test('promise  resolves  with  array',  function()  {    var  search  =

     new  app.Search();    var  result  =  search.fetch(  'cat'  );    requests[0].respond(        200,        {  "Content-­‐type"  :  "text/json"  },        JSON.stringify(  {  results  :  [  'cat'  ]  }  )    );    result.done(function(  data  )  {        equal(  data[0],  'cat'  );    }); }); Friday, November 9, 12
  36. app.Search  =  (function()  {    var  Search  =  function()  {};

       var  processResults  =  function(  resp  )  {        return  resp.results;    };    Search.prototype.fetch  =  function(  query  )  {        return  $.getJSON(  '/data/search.json',  {            q  :  query        }).pipe(  processResults  );    };    return  Search; }()); Friday, November 9, 12
  37. so. Friday, November 9, 12

  38. where to start? Friday, November 9, 12

  39. awesome tools grunt + qunit + phantomjs grunt-mocha grunt-jasmine sinon.js

    chai.js Friday, November 9, 12
  40. $  grunt  init:gruntfile Friday, November 9, 12

  41. Friday, November 9, 12

  42. /*global  module:false*/ var  child_process  =  require('child_process'); module.exports  =  function(grunt)  {

       //  Project  configuration.    grunt.initConfig({        lint:  {            files:  ['lib/**/*.js',  'test/**/*.js',  '!  test/lib/**/*.js',  'www/js/**/*.js']        },        qunit:  {            files:  ['test/**/test-­‐*.html']        },        watch:  {            files:  [  '<config:lint.files>',  'www/templates/*.tmpl'  ],            tasks:  'test'        },        jshint:  {            options:  {                curly:  true,                eqeqeq:  true,                immed:  true,                latedef:  true,                newcap:  true,                noarg:  true,                sub:  true,                undef:  true,                boss:  true,                eqnull:  true,                browser:  true            },            globals:  {                $  :  true,                _  :  true,                RSVP  :  true,                app  :  true            }        },        uglify:  {}    });    grunt.registerTask('default',  'lint  qunit'); }; Friday, November 9, 12
  43. /*global  module:false*/ var  child_process  =  require('child_process'); module.exports  =  function(grunt)  {

       //  Project  configuration.    grunt.initConfig({        lint:  {            files:  ['lib/**/*.js',  'test/**/*.js',  '!  test/lib/**/*.js',  'www/js/**/*.js']        },        qunit:  {            files:  ['test/**/test-­‐*.html']        },        watch:  {            files:  [  '<config:lint.files>',  'www/templates/*.tmpl'  ],            tasks:  'test'        },        jshint:  {            options:  {                curly:  true,                eqeqeq:  true,                immed:  true,                latedef:  true,                newcap:  true,                noarg:  true,                sub:  true,                undef:  true,                boss:  true,                eqnull:  true,                browser:  true            },            globals:  {                $  :  true,                _  :  true,                RSVP  :  true,                app  :  true            }        },        uglify:  {}    });    grunt.registerTask('default',  'lint  qunit'); }; qunit:  {    files:  ['test/**/test-­‐*.html'] }, Friday, November 9, 12
  44. Friday, November 9, 12

  45. Friday, November 9, 12

  46. pinboard.in/u:rmurphey/t:testable-­‐javascript/ bit.ly/WL0R8U Friday, November 9, 12

  47. rmurphey.com • @rmurphey • bocoup.com Friday, November 9, 12