Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

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
  2. Writing Testable JavaScript Rebecca Murphey • Full Frontal 2012 ©

     brianUphoto  http://www.flickr.com/photos/snype451/5752753663/in/photostream/ Friday, November 9, 12
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  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()  ); }); $.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
  10. use constructors to create instances support con gurability keep methods

    simple don’t intermingle responsibilities guiding principles Friday, November 9, 12
  11. 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
  12. test('create  a  likes  view',  function()  {    var  view  =

     new  app.Views.Likes({        el:  '#likes'    });        ok(  view  ); }); Friday, November 9, 12
  13. 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
  14. 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
  15. $.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
  16. 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
  17. 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
  18. 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
  19. module('search  data',  {    setup  :  function()  {    

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

     new  app.Search();    var  result  =  search.fetch(  'cat'  );    ok(  result.then  ); }); Friday, November 9, 12
  21. 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
  22. 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
  23. /*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
  24. /*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