$30 off During Our Annual Pro Sale. View Details »

Writing Testable JavaScript

Writing Testable JavaScript

Slides from my 2012 Full Frontal presentation.

Rebecca Murphey

November 09, 2012
Tweet

More Decks by Rebecca Murphey

Other Decks in Technology

Transcript

  1. Writing
    Testable
    JavaScript
    Rebecca Murphey • Full Frontal 2012
    ©  brianUphoto  http://www.flickr.com/photos/snype451/5752753663/in/photostream/
    Friday, November 9, 12

    View Slide

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

    View Slide

  3. Writing
    Testable
    JavaScript
    Rebecca Murphey • Full Frontal 2012
    ©  brianUphoto  http://www.flickr.com/photos/snype451/5752753663/in/photostream/
    Friday, November 9, 12

    View Slide

  4. Friday, November 9, 12

    View Slide

  5. Friday, November 9, 12

    View Slide

  6. Friday, November 9, 12

    View Slide

  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

    View Slide

  8. Friday, November 9, 12

    View Slide

  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;
           }
       });
       $('',  {
           'class'  :  'pending',
           html  :  'Searching  …'
       }).appendTo(  resultsList.empty()  );
    });
    Friday, November 9, 12

    View Slide

  10. “do the pieces work together as expected?”
    integration tests
    Friday, November 9, 12

    View Slide

  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

    View Slide

  12. Friday, November 9, 12

    View Slide

  13. Friday, November 9, 12

    View Slide

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

    View Slide

  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;
           }
       });
       $('',  {
           'class'  :  'pending',
           html  :  'Searching  …'
       }).appendTo(  resultsList.empty()  );
    });
    Friday, November 9, 12

    View Slide

  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

    View Slide

  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;
           }
       });
       $('',  {
           'class'  :  'pending',
           html  :  'Searching  …'
       }).appendTo(  resultsList.empty()  );
    });
    var  resultsList  =  $(  '#results'  );
    var  liked  =  $(  '#liked'  );
    var  pending  =  false;
    $(  '#searchForm'  ).on(  'submit',  function(  e  )  {
       //  ...
    });
    Friday, November 9, 12

    View Slide

  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;
           }
       });
       $('',  {
           'class'  :  'pending',
           html  :  'Searching  …'
       }).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

    View Slide

  19. Friday, November 9, 12

    View Slide

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

    View Slide

  21. Friday, November 9, 12

    View Slide

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

    View Slide

  23. use constructors to create instances
    support con gurability
    keep methods simple
    don’t intermingle responsibilities
    guiding principles
    Friday, November 9, 12

    View Slide

  24. test rst.
    Friday, November 9, 12

    View Slide

  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();
       $(  '',  {  text:  name  }  ).appendTo(  liked  );
    });
    Friday, November 9, 12

    View Slide

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

    View Slide

  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

    View Slide

  28. app.Views.Likes  =  (function()  {
       var  Likes  =  function(  settings  )  {
           this.$el  =  $(  settings.el  );
       };
       Likes.prototype.add  =  function(  name  )  {
           this.$el.find(  '.no-­‐results'  ).remove();
           $(  '',  {  html  :  name  }  ).appendTo(  this.$el  );
       };
       return  Likes;
    }());
    Friday, November 9, 12

    View Slide

  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

    View Slide

  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:  '[email protected]
       ]);
       
       var  html  =  $(  '#results'  ).html();
       
       ok(  html.match(  'Rebecca'  )  );
       ok(  html.match(  'Dan'  )  );
       equal(  $(  '#results  li'  ).length,  2  );
    });
    Friday, November 9, 12

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  34. test('fetch  returns  a  promise',  function()  {
       var  search  =  new  app.Search();
       var  result  =  search.fetch(  'cat'  );
       ok(  result.then  );
    });
    Friday, November 9, 12

    View Slide

  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

    View Slide

  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

    View Slide

  37. so.
    Friday, November 9, 12

    View Slide

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

    View Slide

  39. awesome tools
    grunt + qunit + phantomjs
    grunt-mocha
    grunt-jasmine
    sinon.js
    chai.js
    Friday, November 9, 12

    View Slide

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

    View Slide

  41. Friday, November 9, 12

    View Slide

  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:  [  '',  '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

    View Slide

  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:  [  '',  '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

    View Slide

  44. Friday, November 9, 12

    View Slide

  45. Friday, November 9, 12

    View Slide

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

    View Slide

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

    View Slide