96

Click here to load reader

Unit Testing JavaScript Applications

Embed Size (px)

DESCRIPTION

An in depth look at mocha and sinon. Slides show how to use both to write unit tests and mock objects for your JavaScript application

Citation preview

Page 1: Unit Testing JavaScript Applications

Mocha First StepsInstalling and running tests

Page 2: Unit Testing JavaScript Applications

Agenda

• JS Unit Testing

• A first Mocha test

• Running tests with Karma

• IDE integration

Page 3: Unit Testing JavaScript Applications

Getting Ready To Test

• JS Unit tests (try) make sure our JS code works well

Page 4: Unit Testing JavaScript Applications

Project Tree

index.html   - src   - main.js   - buttons.js   - player.js   - style   - master.css   - home.css  

Page 5: Unit Testing JavaScript Applications

Project Treeindex.html test.html   - src   - main.js   - buttons.js   - player.js   - style   - master.css   - home.css   - spec   - test_button.js   - test_player.js

Page 6: Unit Testing JavaScript Applications

What Mocha Isn’t

• No UI / CSS testing

• No server testing

Page 7: Unit Testing JavaScript Applications

Testing How

Page 8: Unit Testing JavaScript Applications

Testing Libraries

• Let’s try to write test program for Array

• Verify indexOf(...) actually works

Page 9: Unit Testing JavaScript Applications

Array#indexof

var arr1 = [10, 20, 30, 40];   if ( arr1.indexOf(20) === 1 ) {   console.log('success!'); } else {   console.log('error'); }

Page 10: Unit Testing JavaScript Applications

What Went Wrong

• Hard to debug

• Hard to run automatically

Page 11: Unit Testing JavaScript Applications

We Need …

Page 12: Unit Testing JavaScript Applications

We Need …

Page 13: Unit Testing JavaScript Applications

Testing Libraries

• A testing library tells you how to structure your testing code

• We’ll use mochahttp://visionmedia.github.io/mocha/

Page 14: Unit Testing JavaScript Applications

Hello Mochavar assert = chai.assert; var array = [10,20,30,40];   describe('Array', function() { !  describe('#indexOf()', function() { !    it('should return -1 when the value is not present', function() {             assert.equal(array.indexOf(7), -1);     } );   }); });

Page 15: Unit Testing JavaScript Applications

Hello Mocha

• describe() defines a block

• it() defines functionality

Page 16: Unit Testing JavaScript Applications

Assertions

• Uses a separate assertions library

• I went with Chai

• http://chaijs.com/

Page 17: Unit Testing JavaScript Applications

Running Our Test: Karma

Page 18: Unit Testing JavaScript Applications

Meet Karma

• A test runner for JS

• Integrates with many IDEs

• Integrates with CI servers

• http://karma-runner.github.io/0.10/index.html

Page 19: Unit Testing JavaScript Applications

Karma Architecture

Karma Server

Page 20: Unit Testing JavaScript Applications

Karma Getting Started

# run just once to install npm install karma -g   # create a project directory mkdir myproject cd myproject   # create karma configuration file karma init

Page 21: Unit Testing JavaScript Applications

Karma Config

• Just a JavaScript file

• keys determine how test should run

Page 22: Unit Testing JavaScript Applications

Karma Config

• files is a list of JS files to include in the test

• Can use wildcards

Page 23: Unit Testing JavaScript Applications

Karma Config

• browsers is a list of supported browsers

Page 24: Unit Testing JavaScript Applications

Running Tests

# start a karma server karma start   # execute tests karma run

Page 25: Unit Testing JavaScript Applications

IDE Integration

Page 26: Unit Testing JavaScript Applications

What We Learned

• Mocha is a JS library that helps us write unit tests

• Karma is a JS library that helps us run them

Page 27: Unit Testing JavaScript Applications

Q & A

Page 28: Unit Testing JavaScript Applications

Advanced MochaHow to write awesome tests

Page 29: Unit Testing JavaScript Applications

Agenda

• Flow control: before, after, beforeEach, afterEach

• Writing async tests

• Fixtures and DOM testing

Page 30: Unit Testing JavaScript Applications

Let’s Flowdescribe('Test 1', function() {   it('should do X', function() {     var p1 = new Player('bob');     var p2 = new Player('John');     var game = new GameEngine(p1, p2);       // test stuff with game   });     it('should do Y', function() {     var p1 = new Player('bob');     var p2 = new Player('John');     var game = new GameEngine(p1, p2);       // test stuff with game   }); });

Page 31: Unit Testing JavaScript Applications

Let’s Flowdescribe('Test 1', function() {   it('should do X', function() {     var p1 = new Player('bob');     var p2 = new Player('John');     var game = new GameEngine(p1, p2);       // test stuff with game   });     it('should do Y', function() {     var p1 = new Player('bob');     var p2 = new Player('John');     var game = new GameEngine(p1, p2);       // test stuff with game   }); });

Same code...

Page 32: Unit Testing JavaScript Applications

A Better Scheme• beforeEach() runs

before each test

• also has:

• afterEach() for cleanups

• before() and after() run once in the suite

describe('Test 1', function() {!  var game;! !  beforeEach(function() {!    var p1 = new Player('bob');!    var p2 = new Player('John');!    game = new GameEngine(p1, p2);!  });! !  it('should do X', function() {!    // test stuff with game!  });!! !  it('should do Y', function() {!    // test stuff with game!  });!});

Page 33: Unit Testing JavaScript Applications

Async Testing

Page 34: Unit Testing JavaScript Applications

Async Theory

var x = 10

test x x has the right value testing here is OK

Page 35: Unit Testing JavaScript Applications

Async Theory

$.get(...)

test result Can’t test now, result not yet ready

Page 36: Unit Testing JavaScript Applications

Async Theory

• Async calls take callbacks

• We should tell mocha to wait

Page 37: Unit Testing JavaScript Applications

Async Code

describe('Test 1', function() {     it('should do wait', function(done) {     setTimeout(function() {       // now we can test result       assert(true);       done();     }, 1000);   });   });

Page 38: Unit Testing JavaScript Applications

Async Code

describe('Test 1', function() {     it('should do wait', function(done) {     setTimeout(function() {       // now we can test result       assert(true);       done();     }, 1000);   });   });

Taking a function argument tells mocha the test will only end after it’s called

Page 39: Unit Testing JavaScript Applications

Async Code

describe('Test 1', function() {     it('should do wait', function(done) {     setTimeout(function() {       // now we can test result       assert(true);       done();     }, 1000);   });   });

Calling the callback ends the test

Page 40: Unit Testing JavaScript Applications

Async Notes

• Always call done() or your test will fail on timeout

• Default timeout is 2 seconds

Page 41: Unit Testing JavaScript Applications

Controlling Timeouts

describe('Test 1', function() {   // set suite specific timeout   this.timeout(500);     it('should do wait', function(done) {     // test specific timeout     this.timeout(2000);       }); });

Page 42: Unit Testing JavaScript Applications

Same goes for Ajax

describe('Test 1', function() {   // set suite specific timeout   this.timeout(5000);     it('should get user photo', function(done) {     $.get('profile.png', function(data) {       // run tests on data       done();     });   }); });

Page 43: Unit Testing JavaScript Applications

DOM Testing

Page 44: Unit Testing JavaScript Applications

Theory

body

“Real” HTML “Test” HTML

body

h1

div div

img

Page 45: Unit Testing JavaScript Applications

Theory

body

“Real” HTML “Test” HTML

body

h1

div div

img img

Page 46: Unit Testing JavaScript Applications

Theory

$('img.thumbnail').css({ width: 200, height: 200 });

<img class="thumbmail" src="home.png" />

fixture.html

images.js

Page 47: Unit Testing JavaScript Applications

Using Fixtures

before(function() { fixture_el = document.createElement('div'); fixture_el.id = "fixture"; ! document.body.appendChild(fixture_el); }); !beforeEach(function() { fixture_el.innerHTML = window.__html__["fixture.html"]; }); !

Page 48: Unit Testing JavaScript Applications

Almost Ready

• HTML files are not served by default

• We need to tell karma to serve it

Page 49: Unit Testing JavaScript Applications

Serving HTMLs• Modify files section to include the last

(HTML) pattern

// list of files / patterns to load in the browser files: [ 'lib/**/*.js', 'plugins/**/*.js', 'test/fixtures/*.html', 'spec/*.js' ],

Page 50: Unit Testing JavaScript Applications

Testing a jQuery Plugin

it('should change the header text lowercase', function() { $('.truncate').succinct({ size: 100 }); ! var result = $('.truncate').text(); assert.equal( result.length , 100 ); });

Page 51: Unit Testing JavaScript Applications

Fixtures & DOM

• Define DOM fragments in HTML files

• Load from test suite

• Test and clean up

Page 52: Unit Testing JavaScript Applications

Spying With SinonStubs, Spies and Mock Objects explained

Page 53: Unit Testing JavaScript Applications

Agenda• Reasons to mock

• Vanilla mocking

• How sinon can help

• Stubs and Spies

• Faking timers

• Faking the server

Page 54: Unit Testing JavaScript Applications

Reasons To Mock

Page 55: Unit Testing JavaScript Applications

Reasons To Mock

PersonalData$.ajax setTimeout

Page 56: Unit Testing JavaScript Applications

Reasons To Mock

PersonalData$.ajax setTimeout

Page 57: Unit Testing JavaScript Applications

Reasons To Mock

• PersonalData object can save data to server

• If saving failed, it retries 3 times

Page 58: Unit Testing JavaScript Applications

Reasons To Mock

• Both server and clock are external

• We prefer to test in isolation

Page 59: Unit Testing JavaScript Applications

What We Can Do

• Provide our own $.ajax, that won’t go to the server

• Provide our own setTimeout that won’t wait for the time to pass

Page 60: Unit Testing JavaScript Applications

What We Can Do

• Lab: Given the class here https://gist.github.com/ynonp/6667146

• Write a test case to verify sendData actually retried 3 times

Page 61: Unit Testing JavaScript Applications

What We Can Do

• Solution: https://gist.github.com/ynonp/6667284

Page 62: Unit Testing JavaScript Applications

Mocking Notes

• Solution is far from perfect.

• After the test our “fake” methods remain

• Writing “fake” methods was not trivial

Page 63: Unit Testing JavaScript Applications

This Calls for Sinon

Page 64: Unit Testing JavaScript Applications

About Sinon

• A JS mocking library

• Helps create fake objects

• Helps tracking them

Page 65: Unit Testing JavaScript Applications

About Sinon

• Homepage: http://sinonjs.org/

• Google Group: http://groups.google.com/group/sinonjs

• IRC Channel: #sinon.js on freenode

Page 66: Unit Testing JavaScript Applications

Solving with Sinon

• Here’s how sinon might help us with the previous task

• Code: https://gist.github.com/ynonp/6667378

Page 67: Unit Testing JavaScript Applications

Solution Notes

• Sinon’s fake timer was easier to use than writing our own

• Now we have a synchronous test (instead of async)

Page 68: Unit Testing JavaScript Applications

Let’s Talk About Sinon

Page 69: Unit Testing JavaScript Applications

Spies

• A spy is a function that provides the test code with info about how it was used

Page 70: Unit Testing JavaScript Applications

Spies Demodescribe('Sinon', function() { describe('spies', function() { ! it('should keep count', function() { ! var s = sinon.spy(); s(); assert.isTrue(s.calledOnce); ! s(); assert.isTrue(s.calledTwice); ! s(); assert.equal(s.callCount, 3); ! }); }); });

Page 71: Unit Testing JavaScript Applications

Spy On Existing Funcsdescribe('Sinon', function() { describe('spies', function() { ! it('should keep count', function() { var p = new PersonalData(); var spy = sinon.spy(p, 'sendData'); ! p.sendData(); ! assert.isTrue( spy.calledOnce ); }); }); });

Page 72: Unit Testing JavaScript Applications

Spies Notes

• Full API: http://sinonjs.org/docs/#spies

• Tip: Use as callbacks

Page 73: Unit Testing JavaScript Applications

Spy + Action = Stub

Page 74: Unit Testing JavaScript Applications

Stub Demo

• Let’s fix our starting example

• We’ll replace $.ajax with a stub

• That stub always fails

Page 75: Unit Testing JavaScript Applications

Stub Demovar stub = sinon.stub(jQuery, 'ajax').yieldsTo('error'); !describe('Data', function() { describe('#sendData()', function() { ! it('should retry 3 times before quitting', function() { var p = new PersonalData(); p.sendData(); assert.equal(stub.callCount, 1); }); }); });

Page 76: Unit Testing JavaScript Applications

What Can Stubs Do

var callback = sinon.stub(); !callback.withArgs(42).returns(1); !callback.withArgs(1).throws("TypeError"); !

Page 77: Unit Testing JavaScript Applications

Stubs API• Full Stubs API docs:

http://sinonjs.org/docs/#stubs

• Main actions:

• return stuff

• throw stuff

• call stuff

Page 78: Unit Testing JavaScript Applications

Spies Lab

• Given code here: https://gist.github.com/ynonp/7101081

• Fill in the blanks to make the tests pass

Page 79: Unit Testing JavaScript Applications

Fake Timers

• Use sinon.useFakeTimers() to create a fake timer

• Use clock.restore() to clear fake timers

Page 80: Unit Testing JavaScript Applications

Fake Timers

• Use tick(...) to advance

• Affected methods:

• setTimeout, setInterval, clearTimeout, clearInterval

• Date constructor

Page 81: Unit Testing JavaScript Applications

Fake Servers

• Testing client/server communication is hard

• Use fake servers to simplify it

Page 82: Unit Testing JavaScript Applications

Fake Servers

PersonalData$.ajax

Page 83: Unit Testing JavaScript Applications

Fake Servers

PersonalData$.ajax

Fake

Page 84: Unit Testing JavaScript Applications

Let’s write a test for the following class

1. function Person(id) { 2.   var self = this; 3.   4.   self.load = function() { 5.     var url = '/users/' + id; 6.   7.     $.get('/users/' + id, function(info) { 8.       self.name = info.name; 9.       self.favorite_color = info.favorite_color; 10.     }); 11.   }; 12. }

Page 85: Unit Testing JavaScript Applications

Testing Plan

• Set-up a fake server

• Create a new Person

• call load()

• verify fields data

Page 86: Unit Testing JavaScript Applications

Setting Up The Server

1. var server = sinon.fakeServer.create(); 2.   3. var headers  = {"Content-Type" : "application/json"}; 4. var response = JSON.stringify( 5.                 {"name" : "joe", "favorite_color": "blue" }); 6.   7. server.respondWith("GET", "/users/7", 8.                    [200, headers, response]); 9. // now requesting /user/info.php returns joe's info as a JSON

Page 87: Unit Testing JavaScript Applications

Loading a Person

1. var p = new Person(7); 2. // sends a request 3. p.load(); 4.   5. // now we have 1 pending request, let's fake the response 6. server.respond();

Page 88: Unit Testing JavaScript Applications

Verifying the Data

1. // finally, verify data 2. expect(p.name).to.eq('joe'); 3. expect(p.favorite_color).to.eq('blue'); 4.   5. // and restore AJAX behavior 6. server.restore();

Page 89: Unit Testing JavaScript Applications

Fake Server

• Use respondWith() to set up routes

• Use respond() to send the response

Page 90: Unit Testing JavaScript Applications

Fake Server• Regexps are also supported, so this works:

1. server.respondWith(/\/todo-items\/(\d+)/, function (xhr, id) { 2.     xhr.respond( 3.       200, 4.       { "Content-Type": "application/json" }, 5.       '[{ "id": ' + id + ' }]'); 6. });

Page 91: Unit Testing JavaScript Applications

Fake Server

• For fine grained control, consider fake XMLHttpRequest

• http://sinonjs.org/docs/#server

Page 92: Unit Testing JavaScript Applications

Wrapping Up

Page 93: Unit Testing JavaScript Applications

Wrapping Up

• Unit tests work best in isolation

• Sinon will help you isolate units, by faking their dependencies

Page 94: Unit Testing JavaScript Applications

Wrapping Up

• Write many tests

• Each test verifies a small chunk of code

• Don’t test everything

Page 95: Unit Testing JavaScript Applications

Online Resources• Chai:

http://chaijs.com/

• Mocha: http://visionmedia.github.io/mocha/

• Sinon: http://sinonjs.org/

• Karma (test runner): http://karma-runner.github.io/0.10/index.html

Page 96: Unit Testing JavaScript Applications

Thanks For Listening

• Ynon Perek

• http://ynonperek.com

[email protected]