Download - Stop Making Excuses and Start Testing Your JavaScript

Transcript
Page 1: Stop Making Excuses and Start Testing Your JavaScript

Stop Making Excuses and Start Testing Your JavaScript!

Stop Making!Excuses and!Start Testing !

Your !JavaScript!

Page 2: Stop Making Excuses and Start Testing Your JavaScript

• Senior UI Engineer at Netflix

[email protected]

• @bittersweeryan

About Me

Page 3: Stop Making Excuses and Start Testing Your JavaScript

Today’s Agenda

• Adding Testing to Your Project

• Solving Common Testing Issues

• Writing Testable JavaScript

• Automating Testing

Page 4: Stop Making Excuses and Start Testing Your JavaScript

Adding Testing To Your Project

Page 5: Stop Making Excuses and Start Testing Your JavaScript

Step 1: Pick Your Environment

Page 6: Stop Making Excuses and Start Testing Your JavaScript

TapeJest

Page 7: Stop Making Excuses and Start Testing Your JavaScript

Browser

Jest

Page 8: Stop Making Excuses and Start Testing Your JavaScript

Step 2: Pick Your Dialect

Page 9: Stop Making Excuses and Start Testing Your JavaScript

expectdescribe( 'adder', function(){ it( 'should return zero when no args...', function(){ expect( adder() ).to.equal( 0 ); } ); } );

+

Page 10: Stop Making Excuses and Start Testing Your JavaScript

shoulddescribe( 'adder', function(){ it( 'should return zero when no args...', function(){ adder().should.equal( 0 ); } ); } );

+

Page 11: Stop Making Excuses and Start Testing Your JavaScript

assert

+

//qunit test( 'adder', function(){ ok( adder() === 0, 'should return 0'); } ); !//chai describe( 'adder', function(){ assert.equal( adder() , 0, 'should return 0' ); });

Page 12: Stop Making Excuses and Start Testing Your JavaScript

Step 3: Setup

Page 13: Stop Making Excuses and Start Testing Your JavaScript
Page 14: Stop Making Excuses and Start Testing Your JavaScript

$> npm install -g mocha $> npm install mocha —save-dev $> npm install chai --save-dev

Page 15: Stop Making Excuses and Start Testing Your JavaScript

module.exports = var adder = function(){ var args = Array.prototype.slice.call( arguments ); return args.reduce( function( previous, curr ){ if( !isNaN( Number( curr ) ) ){ return previous + curr; } else{ return curr; } }, 0 ); };

adder.js

Page 16: Stop Making Excuses and Start Testing Your JavaScript

var expect = require( 'chai' ).expect; var adder = require( '../../src/adder' ); !describe( 'adder', function(){ it( 'should add an array of numbers', function(){ expect( adder( 1,2,3) ).to.equal( 6 ); }); });

adderSpec.js

Page 17: Stop Making Excuses and Start Testing Your JavaScript

... describe( 'adder', function(){ it( 'should return zero if no args are passed in', function(){ expect( adder( ) ).to.equal(0); }); }) ...

adderSpec.js

Page 18: Stop Making Excuses and Start Testing Your JavaScript

... it( 'should skip non numbers', function(){ expect( adder( 1,2,'a' ) ).to.equal( 3 ); }); ...

adderSpec.js

Page 19: Stop Making Excuses and Start Testing Your JavaScript

$> mocha tests/spec

Name of your spec directory

Page 20: Stop Making Excuses and Start Testing Your JavaScript
Page 21: Stop Making Excuses and Start Testing Your JavaScript

Browser

Page 22: Stop Making Excuses and Start Testing Your JavaScript

Browser

<html> <head> <title>Jasmine Spec Runner v2.0.0</title> <link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.0/jasmine.css"> ! <script type="text/javascript" src="lib/jasmine-2.0.0/jasmine.js"></script> <script type="text/javascript" src="lib/jasmine-2.0.0/jasmine-html.js"></script> <script type="text/javascript" src="lib/jasmine-2.0.0/boot.js"></script> ! <!-- include source files here... --> <script type="text/javascript" src="../src/adder.js"></script> <!-- include spec files here... --> <script type="text/javascript" src="spec/adderSpec-jasmine.js"></script> </head> <body> </body> </html>

SpecRunner.html

Source Files

Spec Files

Page 23: Stop Making Excuses and Start Testing Your JavaScript

Browser

Page 24: Stop Making Excuses and Start Testing Your JavaScript

Solving Common Testing Issues

Page 25: Stop Making Excuses and Start Testing Your JavaScript

I Have Methods that Call Other Methods.

Page 26: Stop Making Excuses and Start Testing Your JavaScript

Spies/Stubs/Mocks

Page 27: Stop Making Excuses and Start Testing Your JavaScript
Page 28: Stop Making Excuses and Start Testing Your JavaScript

SpiesUser.prototype.addUser = function(){ if(validateUser()){ saveUser(); } else{ addError( 'user not valid' ); } };

Page 29: Stop Making Excuses and Start Testing Your JavaScript

Spiesit('should save a valid user', function(){ var user = new User( 'Ryan','Anklam'); spyOn(User, ‘validate’).and.returnValue( true ); spyOn(User, 'save'); user.add(); expect(user.validate).toHaveBeenCalled(); expect(user.save).toHaveBeenCalled(); });

Page 30: Stop Making Excuses and Start Testing Your JavaScript

Spiesit('should error for an invalid user', function(){ var user = new User( 'Ryan','Anklam'); spyOn(User, ‘validate’).andReturn( false ); spyOn(User, 'addError'); user.add(); expect(user.validate).toHaveBeenCalled(); expect(user.addError).toHaveBeenCalled(); });

Page 31: Stop Making Excuses and Start Testing Your JavaScript

Spies That Return Data User.prototype.listUsers = function(){ var users = this.getUsers(); users.forEach( function( user ){ this.addUserToList( user ); }); };

Page 32: Stop Making Excuses and Start Testing Your JavaScript

Spies That Return Datait('should get a list of users and add them', function(){ spyOn(User, 'getUsers').and.returnValue( [ { firstName : 'Ryan', lastName : 'Anklam' } ] ); spyOn(User, 'addUserToList'); user.listUsers(); expect(user.getUsers).toHaveBeenCalled(); expect(user.addUserToList).toHaveBeenCalled(); });

Page 33: Stop Making Excuses and Start Testing Your JavaScript

Promises

Page 34: Stop Making Excuses and Start Testing Your JavaScript

Returns A PromisegetStringValue: function( req, bundle, key, context ){ var i18n = require("./index"), bundlePromise = this.getBundle( req, bundle ), deferred = q.defer(); ! bundlePromise.then( function( bundle ){ deferred.resolve( bundle.get( i18n.get(key,context) ) ); }, function( err ){ deferred.reject( err ); } ); ! return deferred.promise; }

Page 35: Stop Making Excuses and Start Testing Your JavaScript

chai-as-promised FTW!describe( 'getStringValue function', function(){ ! it('should return a string when it finds a key', function ( ) { return expect( utils.getStringValue( 'foo.bar.baz' ) ) .to.eventually.equal( 'Foo bar baz'] ); }); ! it('should return the key when no key is found', function () { return expect( utils.getStringValue( 'does.not.exist' ) ) .to.eventually.equal( 'does.not.exist' ); }); } );

Page 36: Stop Making Excuses and Start Testing Your JavaScript

I Need To Test the DOM

Page 37: Stop Making Excuses and Start Testing Your JavaScript

Do you REALLY need to?

Page 38: Stop Making Excuses and Start Testing Your JavaScript
Page 39: Stop Making Excuses and Start Testing Your JavaScript

Spy On The DOMfunction addItems( $el, items ){ items.forEach( function( item ){ $el.append( item ); } ); }

Page 40: Stop Making Excuses and Start Testing Your JavaScript

Spy On The DOMit( 'should add items', function(){ var $el = $( '<ul/>' ), items = [ { name : 'test' } ]; spyOn( jQuery.fn, 'append' ); ! addItems( $el, items ); expect( jQuery.fn.append ) .toHaveBeenCalled(); } );

Page 41: Stop Making Excuses and Start Testing Your JavaScript

Jasmine jQuery

https://github.com/velesin/jasmine-jquery

Create Fixtures as .html files

Load fixtures into a test

Make assertions

Page 42: Stop Making Excuses and Start Testing Your JavaScript

Jasmine jQuerybeforeEach( function(){ //reset the fixture before each test loadFixtures('myfixture.html'); }); !it( 'should convert a select to a ul', function(){ $( '.select' ).dropdown(); expect( '.select-list' ).toExist(); });

Page 43: Stop Making Excuses and Start Testing Your JavaScript

qUnit<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="qunit.css"> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"></div> <script src="qunit.js"></script> <script src="tests.js"></script> </body> </html>

Gets reset after every test

Page 44: Stop Making Excuses and Start Testing Your JavaScript

I Have to Test Async Code

Page 45: Stop Making Excuses and Start Testing Your JavaScript

Async Testfunction readConfig( callback ){ ! fs.readFile( config.fileLocation, callback ); }

Page 46: Stop Making Excuses and Start Testing Your JavaScript

Async Testit( 'should read the config file' , function( done ){ var callback = function( readystate ){ expect( readystate ) .to.equal( 'config ready' ); done(); } readConfig( callback ); } );

Page 47: Stop Making Excuses and Start Testing Your JavaScript

Async TestasyncTest( 'should read the config file' , function( ){ var callback = function( readystate ){ ok( readystate === 'config ready' ); start(); } readConfig( callback ); } );

Page 48: Stop Making Excuses and Start Testing Your JavaScript

Writing Testable JavaScript

Page 49: Stop Making Excuses and Start Testing Your JavaScript

Not Testable JavaScript$(function(){ var $list = $( '.todo-list' ), $input = $( '.new-todo' ), todos = $.ajax(...); todos.forEach( function( item, index){ $list.append( '<li class="todo-item">' + item.name + '</li>'); } ); $input.on( 'keyup', function( e ){ if( e.which === 13 ){ $list.append( '<li class="todo-item">' + $(this).val() + '</li>'); $(this).val( '' ); } } );

Repeated Code

Locked in callback

Magic Data

Can’t configure

One and doneAnonymous Function

Anonymous Function

Page 50: Stop Making Excuses and Start Testing Your JavaScript

Refactoring$(function(){ var $list = $( '.todo-list' ), $input = $( '.new-todo' ), todos = $.ajax(...); todos.forEach( function( item, index){ $list.append( '<li class="todo-item">' + item.name + '</li>'); } ); $input.on( 'keyup', function( e ){ if( e.which === 13 ){ $list.append( '<li class="todo-item">' + $(this).val() + '</li>'); $(this).val( '' ); } } );

Can’t configure

Page 51: Stop Making Excuses and Start Testing Your JavaScript

Make It A Constructorvar Todos = function( $list, $input ){ this.$list = $list; this.$input = $input; };

Page 52: Stop Making Excuses and Start Testing Your JavaScript

A Little Setup & First Testdescribe( 'todos', function(){ var todos; beforeEach( function(){ todos = new Todos( $('<ul/>'), $('<input type="text" value="foobar" />') ); }); } );

it('should create an instance', function(){ expect( typeof todos).toEqual( 'object' ); });

Page 53: Stop Making Excuses and Start Testing Your JavaScript

Not Testable JavaScript$(function(){ var $list = $( '.todo-list' ), $input = $( '.new-todo' ), todos = $.ajax(...); todos.forEach( function( item, index){ $list.append( '<li class="todo-item">' + item.name + '</li>'); } ); $input.on( 'keyup', function( e ){ if( e.which === 13 ){ $list.append( '<li class="todo-item">' + $(this).val() + '</li>'); $(this).val( '' ); } } );

Magic Data

Page 54: Stop Making Excuses and Start Testing Your JavaScript

getDataTodos.prototype.getData = function( success, error ){ $.ajax({ ... success : success, error : error ... }); }

Page 55: Stop Making Excuses and Start Testing Your JavaScript

Test getDatait(‘should have a getData fn that returns an array', function( done ){ var callback = function( items ){ expect(items instanceof Array ).toBeTruthy(); done(); }; todos.getData( callback ); });

**Note: more tests would be to properly test this!**

Page 56: Stop Making Excuses and Start Testing Your JavaScript

Refactoring$(function(){ var $list = $( '.todo-list' ), $input = $( '.new-todo' ), todos = $.ajax(...); todos.forEach( function( item, index){ $list.append( '<li class="todo-item">' + item.name + '</li>'); } ); $input.on( 'keyup', function( e ){ if( e.which === 13 ){ $list.append( '<li class="todo-item">' + $(this).val() + '</li>'); $(this).val( '' ); } } );

Anonymous Function

Page 57: Stop Making Excuses and Start Testing Your JavaScript

Refactoring$(function(){ var $list = $( '.todo-list' ), $input = $( '.new-todo' ), todos = $.ajax(...); todos.forEach( function( item, index){ $list.append( '<li class="todo-item">' + item.name + '</li>'); } ); $input.on( 'keyup', function( e ){ if( e.which === 13 ){ $list.append( '<li class="todo-item">' + $(this).val() + '</li>'); $(this).val( '' ); } } );

One and done

Page 58: Stop Making Excuses and Start Testing Your JavaScript

Refactoring$(function(){ var $list = $( '.todo-list' ), $input = $( '.new-todo' ), todos = $.ajax(...); todos.forEach( function( item, index){ $list.append( '<li class="todo-item">' + item.name + '</li>'); } ); $input.on( 'keyup', function( e ){ if( e.which === 13 ){ $list.append( '<li class="todo-item">' + $(this).val() + '</li>'); $(this).val( '' ); } } );

Repeated Code

Page 59: Stop Making Excuses and Start Testing Your JavaScript

addItemTodos.prototype.addItem = function( name ){ this.$list.append( ’<li class="todo-item">' + name + ‘</li>' ); };

Page 60: Stop Making Excuses and Start Testing Your JavaScript

Test addItemit('should have a addItem function', function(){ expect( typeof todos.addItem ).toEqual( 'function' ); }); !it('should try to add an item to the DOM', function(){ spyOn( jQuery.fn, 'append' ); todos.addItem( 'Learn JS Testing' ); expect( jQuery.fn.append ) .toHaveBeenCalledWith( '<li class="todo-item">Learn JS Testing</li>' ); });

Page 61: Stop Making Excuses and Start Testing Your JavaScript

addItemsTodos.prototype.addItems = function( items ){ items.forEach( function( item, index){ this.addItem( item.name ); }, this ); };

Page 62: Stop Making Excuses and Start Testing Your JavaScript

Test addItemsit('should have a addItems function', function(){ spyOn( todos, 'addItem' ); todos.addItems( [{name:'foo'},{name:'bar'},{name:'baz'}] ); expect( todos.addItem ).toHaveBeenCalledWith( 'foo' ); expect( todos.addItem ).toHaveBeenCalledWith( 'bar' ); expect( todos.addItem ).toHaveBeenCalledWith( 'baz' ); });

Page 63: Stop Making Excuses and Start Testing Your JavaScript

Not Testable JavaScript$(function(){ var $list = $( '.todo-list' ), $input = $( '.new-todo' ), todos = $.ajax(...); todos.forEach( function( item, index){ $list.append( '<li class="todo-item">' + item.name + '</li>'); } ); $input.on( 'keyup', function( e ){ if( e.which === 13 ){ $list.append( '<li class="todo-item">' + $(this).val() + '</li>'); $(this).val( '' ); } } );

Anonymous Function

Page 64: Stop Making Excuses and Start Testing Your JavaScript

handleKeyPressTodos.prototype.handleKeypress = function( e ){ if( e.which === 13 ){ this.addItem( this.$input.val() ); this.$input.val( '' ); } };

Page 65: Stop Making Excuses and Start Testing Your JavaScript

Test handleKeypressit('should have a handleKeypress function', function(){ expect( typeof todos.handleKeypress ) .toEqual( 'function' ); }); !it('should handle a keypress', function(){ spyOn( todos, 'addItem' ); todos.handleKeypress( { which: 13 } ); expect( todos.addItem ) .toHaveBeenCalledWith( 'foobar' ); });

Page 66: Stop Making Excuses and Start Testing Your JavaScript

Refactoring$(function(){ var $list = $( '.todo-list' ), $input = $( '.new-todo' ), todos = $.ajax(...); todos.forEach( function( item, index){ $list.append( '<li class="todo-item">' + item.name + '</li>'); } ); $input.on( 'keyup', function( e ){ if( e.which === 13 ){ $list.append( '<li class="todo-item">' + $(this).val() + '</li>'); $(this).val( '' ); } } );

Locked in callback

Page 67: Stop Making Excuses and Start Testing Your JavaScript

Init functionTodos.prototype.init = function(){ this.$input.on( 'keyup', $.proxy( this.handleKeypress, this ) ); this.getData( this.addItems ); };

Page 68: Stop Making Excuses and Start Testing Your JavaScript

Test Initit('should have an init method', function(){ expect( typeof todos.init ).toEqual( 'function' ); }); !it('should setup a handler and additems', function(){ spyOn( jQuery.fn, 'on' ); spyOn( todos, 'getData' ); todos.init(); expect( jQuery.fn.on ).toHaveBeenCalled(); expect( todos.getData ) .toHaveBeenCalledWith( todos.addItems ); } );

Page 69: Stop Making Excuses and Start Testing Your JavaScript

A Few More Tips

• Write maintainable tests • Avoid Singletons • Use named functions instead of anonymous

callbacks • Keep your functions small

Page 70: Stop Making Excuses and Start Testing Your JavaScript

Automating Testing

Page 71: Stop Making Excuses and Start Testing Your JavaScript

The key to successful testing is making writing and running tests

easy.

Page 72: Stop Making Excuses and Start Testing Your JavaScript

The key to successful testing is making writing and running tests

easy.

Page 73: Stop Making Excuses and Start Testing Your JavaScript

The key to successful testing is making writing and running tests

easy.

Page 74: Stop Making Excuses and Start Testing Your JavaScript

The key to successful testing is making writing and running tests

easy.

Page 75: Stop Making Excuses and Start Testing Your JavaScript

Using NPM

{ ... "scripts": { "test": "mocha tests/spec" }, ... }

Package.json

Page 76: Stop Making Excuses and Start Testing Your JavaScript

Running Build Tests

$> npm test

Page 77: Stop Making Excuses and Start Testing Your JavaScript

Using Your Build Tool

npm install -g grunt-cli

npm install grunt-contrib-jasmine

npm install grunt

Page 78: Stop Making Excuses and Start Testing Your JavaScript

gruntfile.jsmodule.exports = function(grunt) { grunt.initConfig({ jasmine : { tests: { src: ['jquery.js','todos-better.js'], options: { specs: 'tests/spec/todoSpec.js' } } } watch: { tests: { files: ['**/*.js'], tasks: ['jasmine'], } } }); grunt.loadNpmTasks('grunt-contrib-jasmine', ‘watch' ); };

Page 79: Stop Making Excuses and Start Testing Your JavaScript

Running Build Tests

$> grunt jasmine

Page 80: Stop Making Excuses and Start Testing Your JavaScript

Testem

npm install -g testem

Page 81: Stop Making Excuses and Start Testing Your JavaScript

testem.json{ "src_files" : [ "jquery.js", "todos-better.js", "tests/spec/todoSpec.js" ], "launch_in_dev" : [ "PhantomJS", “chrome”, “firefox” ], "framework" : "jasmine2" }

Page 82: Stop Making Excuses and Start Testing Your JavaScript

Testem

$> testem

Page 83: Stop Making Excuses and Start Testing Your JavaScript

Questions?

Page 84: Stop Making Excuses and Start Testing Your JavaScript

Thank You

[email protected]@bittersweetryan