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
220
Making It Better Without Making It Over (Front Porch)
rmurphey
1
200
CascadiaJS: Making it Better Without Making it Over
rmurphey
2
160
Making it Better without Making it Over
rmurphey
1
220
Making It Better Without Making It Over
rmurphey
0
320
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
340
Apps That Talk Back (Codementor)
rmurphey
1
6.4k
Ain't No Party Like a Third-Party JS Party
rmurphey
1
270
Other Decks in Technology
See All in Technology
AWS re:Invent 2024 recap in 20min / JAWSUG 千葉 2025.1.14
shimy
1
110
今から、 今だからこそ始める Terraform で Azure 管理 / Managing Azure with Terraform: The Perfect Time to Start
nnstt1
0
250
Amazon Route 53, 待ちに待った TLSAレコードのサポート開始
kenichinakamura
0
190
コロプラのオンボーディングを採用から語りたい
colopl
5
1.4k
AIアプリケーション開発でAzure AI Searchを使いこなすためには
isidaitc
1
140
RubyでKubernetesプログラミング
sat
PRO
4
160
2025年に挑戦したいこと
molmolken
0
200
iPadOS18でフローティングタブバーを解除してみた
sansantech
PRO
1
160
【NGK2025S】動物園(PINTO_model_zoo)に遊びに行こう
kazuhitotakahashi
0
290
タイミーのデータ活用を支えるdbt Cloud導入とこれから
ttccddtoki
2
350
Git scrapingで始める継続的なデータ追跡 / Git Scraping
ohbarye
5
530
AWSマルチアカウント統制環境のすゝめ / 20250115 Mitsutoshi Matsuo
shift_evolve
0
120
Featured
See All Featured
Fashionably flexible responsive web design (full day workshop)
malarkey
406
66k
The Power of CSS Pseudo Elements
geoffreycrofte
74
5.4k
StorybookのUI Testing Handbookを読んだ
zakiyama
28
5.4k
Bash Introduction
62gerente
610
210k
Designing Experiences People Love
moore
139
23k
GraphQLとの向き合い方2022年版
quramy
44
13k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
33
2.7k
GitHub's CSS Performance
jonrohan
1030
460k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
330
21k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
6
500
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
7
580
Dealing with People You Can't Stand - Big Design 2015
cassininazir
365
25k
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