Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Writing Testable JavaScript
Search
Rebecca Murphey
November 09, 2012
Technology
6.1k
29
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Writing Testable JavaScript
Slides from my 2012 Full Frontal presentation.
Rebecca Murphey
November 09, 2012
More Decks by Rebecca Murphey
See All by Rebecca Murphey
You Can't Always Get What You Want: A Year of Leading Change
rmurphey
0
310
Making It Better Without Making It Over (Front Porch)
rmurphey
1
260
CascadiaJS: Making it Better Without Making it Over
rmurphey
2
250
Making it Better without Making it Over
rmurphey
1
280
Making It Better Without Making It Over
rmurphey
0
400
HTTP/2 is here, now let's make it easy
rmurphey
7
8.9k
Deploying client-side apps, 1000 (or so) at a time
rmurphey
1
450
Apps That Talk Back (Codementor)
rmurphey
1
6.5k
Ain't No Party Like a Third-Party JS Party
rmurphey
1
370
Other Decks in Technology
See All in Technology
全PRの83%が AIレビューだけでマージできるようになった開発組織は その後どうなったか @2026/10
athug
0
140
継続的なAIコスト最適化
bgpat
1
280
1万名の社員が使う認証基盤で どう信頼性を担保するか?
kairim0
0
230
ハードウェアコンペでもAI駆動開発が進んでいる話
iotengineer22
1
680
AgentCoreで実践するハーネスエンジニアリング
yakumo
1
300
SREでアラート疲れを 解決しよう!
kairim0
1
220
パッチ更新でterraform initが落ちた日
grimoh
1
120
FactoryBotアンチパターン / factory_bot anti-patterns
toshimaru
0
150
ファミコンでPHPを動かす / PHP on the Famicom side b
tomzoh
0
140
ブラウザとサーバーから考えるAI時代のフロントエンド監視
shogo4131
3
760
12 個 Tool と、 4,096 Token
hlum
0
120
Incremental HTTP
kazuho
5
2.1k
Featured
See All Featured
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
46k
Mind Mapping
helmedeiros
1
400
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.6k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
300
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
How to Ace a Technical Interview
jacobian
280
24k
Ethics towards AI in product and experience design
skipperchong
2
420
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
1k
Raft: Consensus for Rubyists
vanstee
142
7.7k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
203
77k
The SEO identity crisis: Don't let AI make you average
varn
0
580
What the history of the web can teach us about the future of AI
inesmontani
PRO
2
730
Transcript
Writing Testable JavaScript Rebecca Murphey • Full Frontal 2012 ©
brianUphoto http://www.flickr.com/photos/snype451/5752753663/in/photostream/ Friday, November 9, 12
rmurphey.com • @rmurphey • bocoup.com Friday, November 9, 12
Writing Testable JavaScript Rebecca Murphey • Full Frontal 2012 ©
brianUphoto http://www.flickr.com/photos/snype451/5752753663/in/photostream/ Friday, November 9, 12
Friday, November 9, 12
Friday, November 9, 12
Friday, November 9, 12
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
Friday, November 9, 12
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 …' }).appendTo( resultsList.empty() ); }); Friday, November 9, 12
“do the pieces work together as expected?” integration tests Friday,
November 9, 12
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
Friday, November 9, 12
Friday, November 9, 12
“given input x, is the output y?” unit tests Friday,
November 9, 12
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 …' }).appendTo( resultsList.empty() ); }); Friday, November 9, 12
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
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 …' }).appendTo( resultsList.empty() ); }); var resultsList = $( '#results' ); var liked = $( '#liked' ); var pending = false; $( '#searchForm' ).on( 'submit', function( e ) { // ... }); Friday, November 9, 12
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 …' }).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
Friday, November 9, 12
setup presentation & interaction application state data/server communication Friday,
November 9, 12
Friday, November 9, 12
search data application state glue Friday, November 9, 12
use constructors to create instances support con gurability keep methods
simple don’t intermingle responsibilities guiding principles Friday, November 9, 12
test rst. Friday, November 9, 12
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
test('create a likes view', function() { var view =
new app.Views.Likes({ el: '#likes' }); ok( view ); }); Friday, November 9, 12
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
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
$.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
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
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
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
module('search data', { setup : function() {
xhr = sinon.useFakeXMLHttpRequest(); requests = []; xhr.onCreate = function( req ) { requests.push( req ); }; }, teardown : function() { xhr.restore(); } }); Friday, November 9, 12
test('fetch returns a promise', function() { var search =
new app.Search(); var result = search.fetch( 'cat' ); ok( result.then ); }); Friday, November 9, 12
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
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
so. Friday, November 9, 12
where to start? Friday, November 9, 12
awesome tools grunt + qunit + phantomjs grunt-mocha grunt-jasmine sinon.js
chai.js Friday, November 9, 12
$ grunt init:gruntfile Friday, November 9, 12
Friday, November 9, 12
/*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
/*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
Friday, November 9, 12
Friday, November 9, 12
pinboard.in/u:rmurphey/t:testable-‐javascript/ bit.ly/WL0R8U Friday, November 9, 12
rmurphey.com • @rmurphey • bocoup.com Friday, November 9, 12