Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Writing Testable JavaScript
Search
Rebecca Murphey
November 09, 2012
Technology
29
5.9k
Writing Testable JavaScript
Slides from my 2012 Full Frontal presentation.
Rebecca Murphey
November 09, 2012
Tweet
Share
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
250
Making It Better Without Making It Over (Front Porch)
rmurphey
1
210
CascadiaJS: Making it Better Without Making it Over
rmurphey
2
190
Making it Better without Making it Over
rmurphey
1
230
Making It Better Without Making It Over
rmurphey
0
340
HTTP/2 is here, now let's make it easy
rmurphey
7
8.8k
Deploying client-side apps, 1000 (or so) at a time
rmurphey
1
370
Apps That Talk Back (Codementor)
rmurphey
1
6.4k
Ain't No Party Like a Third-Party JS Party
rmurphey
1
290
Other Decks in Technology
See All in Technology
解消したはずが…技術と人間のエラーが交錯する恐怖体験
lamaglama39
0
190
【CEDEC2025】大規模言語モデルを活用したゲーム内会話パートのスクリプト作成支援への取り組み
cygames
PRO
2
770
Kiroから考える AIコーディングツールの潮流
s4yuba
4
670
2025-07-31: GitHub Copilot Agent mode at Vibe Coding Cafe (15min)
chomado
2
380
Mambaで物体検出 完全に理解した
shirarei24
2
210
Bet "Bet AI" - Accelerating Our AI Journey #BetAIDay
layerx
PRO
4
1.5k
「AIと一緒にやる」が当たり前になるまでの奮闘記
kakehashi
PRO
3
100
生成AI導入の効果を最大化する データ活用戦略
ham0215
0
110
いかにして命令の入れ替わりについて心配するのをやめ、メモリモデルを愛するようになったか(改)
nullpo_head
2
150
JAWS AI/ML #30 AI コーディング IDE "Kiro" を触ってみよう
inariku
3
290
Claude Codeが働くAI中心の業務システム構築の挑戦―AIエージェント中心の働き方を目指して
os1ma
9
1.5k
ビジネス文書に特化した基盤モデル開発 / SaaSxML_Session_2
sansan_randd
0
260
Featured
See All Featured
ReactJS: Keep Simple. Everything can be a component!
pedronauck
667
120k
A better future with KSS
kneath
238
17k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
229
22k
Measuring & Analyzing Core Web Vitals
bluesmoon
7
540
Raft: Consensus for Rubyists
vanstee
140
7k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
26
3k
GraphQLとの向き合い方2022年版
quramy
49
14k
Reflections from 52 weeks, 52 projects
jeffersonlam
351
21k
Music & Morning Musume
bryan
46
6.7k
The Cult of Friendly URLs
andyhume
79
6.5k
Unsuck your backbone
ammeep
671
58k
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