Introduction to 
{ JavaScript Testing } 
Ran Mizrahi (@ranm8) 
Founder and CEO @ CoCycles
About { Me } 
• Founder and CEO of CoCycles. 
• Former Open Source Dpt. Leader of CodeOasis. 
• Architected and lead the development of the Wix App Market. 
• Full-stack and hands-on software engineer.
Why Do Software Projects { Fail } ? 
Production Maintenance 
• Deliver late or over budget. 
• Deliver the wrong thing. 
• Unstable in production. 
• Expensive maintenance. 
• Long adjustment to market 
needs. 
• Long development cycles.
Why Do Software Projects { Fail } ?
Untestable { Code }… 
function createUser(properties) { 
var user = { 
firstName: properties.firstName, 
lastName: properties.lastName, 
username: properties.username, 
mail: properties.mail 
}; 
var fullName = User.firstName + ' ' + User.lastName; 
// Make sure user is valid 
if (!user.firstName || !user.lastName) { 
throw new Error('First or last name are not valid!'); 
} else if(typeof user.mail === 'string' && user.mail.match(new RegExp(/^w+@[a-zA-Z_]+?.[a-zA-Z]{ 
2,3}$/)) === null) { 
throw new Error('Mail is not valid'); 
} else if (!user.username) { 
throw new Error('Username is not valid'); 
} 
$.post('/user', { 
fullName: fullName, 
userName: user.username, 
mail: user.mail 
}, function(data) { 
var message; 
if (data.code === 200) { 
message = 'User saved successfully!'; 
} else { 
message = 'Operation was failed!'; 
} 
$('#some-div').animate({ 
'margin-left': $(window).width() 
}, 1000, function() { 
$(this).html(message); 
}); 
}); 
}
The problems with untestable code: 
• Tightly coupled. 
• No separation of concerns. 
• Not readable. 
• Not predictable. 
> 
• Global states. 
• Long methods. 
• Large classes/objects. 
• Hard to maintain. 
• High learning curve. 
• Stability issues. 
• You can never expect 
problems before they 
occur 
Why Test Your { Code } ?
{ Test-Driven Development } To The Rescue 
Methodology for using automated unit 
tests to drive software design, quality 
and stability.
{ Test-Driven Development } To The Rescue 
How it’s done : 
• First the developer writes 
a failing test case that 
defines a desired 
functionality to the 
software. 
• Makes the code pass 
those tests. 
• Refactor the code to meet 
standards.
Seems Great But How Much Longer Does { TDD Take } ? 
My experience: 
• Initial progress will be slower. 
• Greater consistency. 
• Long tern cost is drastically 
lower 
• After getting used to it, you 
can write TDD faster (-: 
Studies: 
• Takes 15-30% longer. 
• 45-80% less bugs. 
• Fixing bugs later on is 
dramatically faster.
The { Three Rules } of TDD 
Rule #1 
Your code should always fail before you implement the code 
Rule #2 
Implement the simplest code possible to pass your tests. 
Rule #3 
Refactor, refactor and refractor - There is no shame in refactoring.
{ BDD } Behaviour-Driven Development 
What exactly are we testing?! 
Test-Driven Development
{ BDD } Behaviour-Driven Development 
• Originally started in 2003 by Dan North, author of JBehave, the 
first BDD tool. 
• Based on the TDD methodology. 
• Aims to provide tools for both developers and business (e.g. 
product manager, etc.) to share development process together. 
• The steps of BDD : 
• Developers and business personas write specification together. 
• Developer writes tests based on specs and make them fail. 
• Write code to pass those tests. 
• Refactor, refactor, refactor...
{ BDD } Behaviour-Driven Development 
Feature: ls 
In order to see the directory structure 
As a UNIX user 
I need to be able to list the current directory's contents 
Scenario: List 2 files in a directory 
Given I am in a directory "test" 
And I have a file named "foo" 
And I have a file named "bar" 
When I run "ls" 
Then I should get: 
""" 
bar 
foo 
"""
Main { Test Types } 
• Unit Testing 
• Integration Testing 
• Functional Testing
{ Challenges } Testing JavaScript 
• Async tests: 
• Testing async methods can be tricky. 
• Define tests timeout. 
• Indicate when test is completed in callback. 
• Assert on callback. 
• DOM: 
• Testing DOM is a difficult task. 
• The key is to separate your controller and model logic from 
DOM and test those only. 
• Testing DOM is done using functional testing (e.g. WebDriver, 
etc.)
TDD/BDD Using { Mocha and ChaiJS } 
Mocha 
Mocha is a feature-rich JavaScript test frameworks running on 
node and the browser, making asynchronies tests easy. 
Main features: 
• Supports both TDD and BDD styles. 
• Simple async testing. 
• Both browser and node support. 
• Proper exit status for CI support. 
• node.js debugger support. 
• Highly flexible, choose and join the pieces yourself (spy library, 
assertion library, etc.).
TDD/BDD Using { Mocha and ChaiJS } 
ChaiJS 
Chai is a BDD / TDD assertion library for node and the browser 
that can be paired with any JavaScript testing framework. 
Main features: 
• BDD/TDD style. 
• Compatible with all test frameworks. 
• Both node.js and browser compatible. 
• Standalone assertion library. 
• Extendable
TDD/BDD Using { Mocha and ChaiJS } 
Installing Mocha and Chai 
Install mocha globally using npm: 
$ npm install mocha -g 
Install Chai (Locally): 
$ npm install chai
TDD/BDD Using { Mocha and ChaiJS } 
“Normal” test: 
var expect = require(‘chai').expect; 
describe('Array', function() { 
describe('#indexOf()', function() { 
it('expect -1 when the value is not present', function() { 
var array = [1, 2, 3]; 
expect(array.indexOf(4)).to.be(-1); 
}); 
}); 
}); 
Run it.. 
$ mocha --reporter spec 
Array 
#indexOf() 
✓ Expect -1 when the value is not present 
1 test complete (5 ms)
TDD/BDD Using { Mocha and ChaiJS } 
Async test: 
var expect = require(‘chai').expect; 
function asyncCall(val ,callback) { 
var prefix = ' - '; 
setTimeout(function() { 
var newString = val + prefix + 'OK'; 
callback(newString); 
}, 500); 
} 
describe('asyncCall', function() { 
it('Add suffix that prefixed with - to the given string', function(done) { 
var testVal = 'Foo'; 
asyncCall(testVal, function(response) { 
expect(response).to.contain(testVal + ' - OK'); 
done(); 
}); 
}); 
}); 
Let’s run it...
Back To { Our Code }
First, Let’s { Write The Tests } 
function createUser(properties) { 
var user = { 
firstName: properties.firstName, 
lastName: properties.lastName, 
username: properties.username, 
mail: properties.mail 
}; 
var fullName = User.firstName + ' ' + User.lastName; 
// Make sure user is valid 
if (!user.firstName || !user.lastName) { 
throw new Error('First or last name are not valid!'); 
} else if(typeof user.mail === 'string' && user.mail.match(new RegExp(/^w+@[a-zA-Z_]+?.[a-zA-Z]{ 
2,3}$/)) === null) { 
throw new Error('Mail is not valid'); 
} else if (!user.username) { 
throw new Error('Username is not valid'); 
} 
$.post('/user', { 
fullName: fullName, 
userName: user.username, 
mail: user.mail 
}, function(data) { 
var message; 
if (data.code === 200) { 
message = 'User saved successfully!'; 
} else { 
message = 'Operation was failed!'; 
} 
$('#some-div').animate({ 
'margin-left': $(window).width() 
}, 1000, function() { 
$(this).html(message); 
}); 
}); 
}
First, Let’s { Write The Tests } 
What to test in our case: 
• Full name concatenation. 
• API call data. 
• Request callback. 
What not to test : 
• DOM manipulations - Functional testing (e.g. WebDriver). 
• API requests - Integration testing.
First, Let’s { Write The Unit Tests } 
describe('#saveUser()', function() { 
it('should call http service with method POST, path /user, and the user object', function() { 
}); 
it('should compose the full name in to the user object', function() { 
}); 
it('should only return the payload from the response object', function() { 
}); 
}); 
});
The { Implementation } 
function userService($http, baseUrl) { 
baseUrl = baseUrl || 'http://google.com/api'; 
function composeFullName(firstName, lastName) { 
return firstName + ' ' + lastName; 
} 
function returnPayload(response) { 
return response.payload; 
} 
function execute(path, body, method) { 
return $http({ 
url: baseUrl + path, 
method: method || 'GET', 
data: body 
}); 
} 
return { 
saveUser: function(user) { 
user.fullName = composeFullName(user.firstName, user.lastName); 
return execute('/user', user, 'POST').then(returnPayload); 
} 
}; 
}
Implement { Our Unit Tests } 
describe('user service', function() { 
var userService, 
httpMock, 
thenFunc; 
function createHttpMock() { 
thenFunc = sinon.stub(); 
httpMock = sinon.stub().returns({ then: thenFunc }); 
} 
beforeEach(function() { 
createHttpMock(); 
userService = UserService(httpMock); 
}); 
function getUser() { 
return { firstName: 'Ran', lastName: 'Mizrahi' }; 
}
Implement { Our Unit Tests } 
describe('#saveUser()', function() { 
var user; 
beforeEach(function() { 
user = getUser(); 
userService.saveUser(user); 
}); 
it('should call http service with method POST, path /user, and the user object', function() { 
expect(httpMock).to.have.been.calledWith({ 
url: 'http://google.com/api/user', 
method: 'POST', 
data: user 
}); 
}); 
it('should compose the full name in to the user object', function() { 
expect(user.fullName).to.equal('Ran Mizrahi'); 
}); 
it('should only return the payload from the response object', function() { 
var returnPayload = thenFunc.args[0][0]; 
expect(returnPayload({ payload: 'Hello!!!' })).to.equal('Hello!!!'); 
}); 
}); 
});
Run Those { Tests Again }
Running The { Tests } 
mocha tests can run in different environments and formats: 
• Browser - using mocha.js (see example) 
• For CI automation use JSTestDriver. 
• CLI - as demonstrated before using the “mocha” command. 
• CI (e.g. xunit) - $ mocha test/asyncTest.js --reporter xunit. 
• Many other formats (JSON, HTML, list, Spec, etc.)
Benefits of { Testing The Code } 
• Short feedback/testing cycle. 
• High code coverage of tests that can be at run any time to 
provide feedback that the software is functioning. 
• Provides detailed spec/docs of the application. 
• Less time spent on debugging and refactoring. 
• Know what breaks early on. 
• Enforces code quality and simplicity. 
• Helps separating units to responsibilities.
Thank you! 
Questions?

Intro To JavaScript Unit Testing - Ran Mizrahi

  • 1.
    Introduction to {JavaScript Testing } Ran Mizrahi (@ranm8) Founder and CEO @ CoCycles
  • 2.
    About { Me} • Founder and CEO of CoCycles. • Former Open Source Dpt. Leader of CodeOasis. • Architected and lead the development of the Wix App Market. • Full-stack and hands-on software engineer.
  • 3.
    Why Do SoftwareProjects { Fail } ? Production Maintenance • Deliver late or over budget. • Deliver the wrong thing. • Unstable in production. • Expensive maintenance. • Long adjustment to market needs. • Long development cycles.
  • 4.
    Why Do SoftwareProjects { Fail } ?
  • 5.
    Untestable { Code}… function createUser(properties) { var user = { firstName: properties.firstName, lastName: properties.lastName, username: properties.username, mail: properties.mail }; var fullName = User.firstName + ' ' + User.lastName; // Make sure user is valid if (!user.firstName || !user.lastName) { throw new Error('First or last name are not valid!'); } else if(typeof user.mail === 'string' && user.mail.match(new RegExp(/^w+@[a-zA-Z_]+?.[a-zA-Z]{ 2,3}$/)) === null) { throw new Error('Mail is not valid'); } else if (!user.username) { throw new Error('Username is not valid'); } $.post('/user', { fullName: fullName, userName: user.username, mail: user.mail }, function(data) { var message; if (data.code === 200) { message = 'User saved successfully!'; } else { message = 'Operation was failed!'; } $('#some-div').animate({ 'margin-left': $(window).width() }, 1000, function() { $(this).html(message); }); }); }
  • 6.
    The problems withuntestable code: • Tightly coupled. • No separation of concerns. • Not readable. • Not predictable. > • Global states. • Long methods. • Large classes/objects. • Hard to maintain. • High learning curve. • Stability issues. • You can never expect problems before they occur Why Test Your { Code } ?
  • 7.
    { Test-Driven Development} To The Rescue Methodology for using automated unit tests to drive software design, quality and stability.
  • 8.
    { Test-Driven Development} To The Rescue How it’s done : • First the developer writes a failing test case that defines a desired functionality to the software. • Makes the code pass those tests. • Refactor the code to meet standards.
  • 9.
    Seems Great ButHow Much Longer Does { TDD Take } ? My experience: • Initial progress will be slower. • Greater consistency. • Long tern cost is drastically lower • After getting used to it, you can write TDD faster (-: Studies: • Takes 15-30% longer. • 45-80% less bugs. • Fixing bugs later on is dramatically faster.
  • 10.
    The { ThreeRules } of TDD Rule #1 Your code should always fail before you implement the code Rule #2 Implement the simplest code possible to pass your tests. Rule #3 Refactor, refactor and refractor - There is no shame in refactoring.
  • 11.
    { BDD }Behaviour-Driven Development What exactly are we testing?! Test-Driven Development
  • 12.
    { BDD }Behaviour-Driven Development • Originally started in 2003 by Dan North, author of JBehave, the first BDD tool. • Based on the TDD methodology. • Aims to provide tools for both developers and business (e.g. product manager, etc.) to share development process together. • The steps of BDD : • Developers and business personas write specification together. • Developer writes tests based on specs and make them fail. • Write code to pass those tests. • Refactor, refactor, refactor...
  • 13.
    { BDD }Behaviour-Driven Development Feature: ls In order to see the directory structure As a UNIX user I need to be able to list the current directory's contents Scenario: List 2 files in a directory Given I am in a directory "test" And I have a file named "foo" And I have a file named "bar" When I run "ls" Then I should get: """ bar foo """
  • 14.
    Main { TestTypes } • Unit Testing • Integration Testing • Functional Testing
  • 15.
    { Challenges }Testing JavaScript • Async tests: • Testing async methods can be tricky. • Define tests timeout. • Indicate when test is completed in callback. • Assert on callback. • DOM: • Testing DOM is a difficult task. • The key is to separate your controller and model logic from DOM and test those only. • Testing DOM is done using functional testing (e.g. WebDriver, etc.)
  • 16.
    TDD/BDD Using {Mocha and ChaiJS } Mocha Mocha is a feature-rich JavaScript test frameworks running on node and the browser, making asynchronies tests easy. Main features: • Supports both TDD and BDD styles. • Simple async testing. • Both browser and node support. • Proper exit status for CI support. • node.js debugger support. • Highly flexible, choose and join the pieces yourself (spy library, assertion library, etc.).
  • 17.
    TDD/BDD Using {Mocha and ChaiJS } ChaiJS Chai is a BDD / TDD assertion library for node and the browser that can be paired with any JavaScript testing framework. Main features: • BDD/TDD style. • Compatible with all test frameworks. • Both node.js and browser compatible. • Standalone assertion library. • Extendable
  • 18.
    TDD/BDD Using {Mocha and ChaiJS } Installing Mocha and Chai Install mocha globally using npm: $ npm install mocha -g Install Chai (Locally): $ npm install chai
  • 19.
    TDD/BDD Using {Mocha and ChaiJS } “Normal” test: var expect = require(‘chai').expect; describe('Array', function() { describe('#indexOf()', function() { it('expect -1 when the value is not present', function() { var array = [1, 2, 3]; expect(array.indexOf(4)).to.be(-1); }); }); }); Run it.. $ mocha --reporter spec Array #indexOf() ✓ Expect -1 when the value is not present 1 test complete (5 ms)
  • 20.
    TDD/BDD Using {Mocha and ChaiJS } Async test: var expect = require(‘chai').expect; function asyncCall(val ,callback) { var prefix = ' - '; setTimeout(function() { var newString = val + prefix + 'OK'; callback(newString); }, 500); } describe('asyncCall', function() { it('Add suffix that prefixed with - to the given string', function(done) { var testVal = 'Foo'; asyncCall(testVal, function(response) { expect(response).to.contain(testVal + ' - OK'); done(); }); }); }); Let’s run it...
  • 21.
    Back To {Our Code }
  • 22.
    First, Let’s {Write The Tests } function createUser(properties) { var user = { firstName: properties.firstName, lastName: properties.lastName, username: properties.username, mail: properties.mail }; var fullName = User.firstName + ' ' + User.lastName; // Make sure user is valid if (!user.firstName || !user.lastName) { throw new Error('First or last name are not valid!'); } else if(typeof user.mail === 'string' && user.mail.match(new RegExp(/^w+@[a-zA-Z_]+?.[a-zA-Z]{ 2,3}$/)) === null) { throw new Error('Mail is not valid'); } else if (!user.username) { throw new Error('Username is not valid'); } $.post('/user', { fullName: fullName, userName: user.username, mail: user.mail }, function(data) { var message; if (data.code === 200) { message = 'User saved successfully!'; } else { message = 'Operation was failed!'; } $('#some-div').animate({ 'margin-left': $(window).width() }, 1000, function() { $(this).html(message); }); }); }
  • 23.
    First, Let’s {Write The Tests } What to test in our case: • Full name concatenation. • API call data. • Request callback. What not to test : • DOM manipulations - Functional testing (e.g. WebDriver). • API requests - Integration testing.
  • 24.
    First, Let’s {Write The Unit Tests } describe('#saveUser()', function() { it('should call http service with method POST, path /user, and the user object', function() { }); it('should compose the full name in to the user object', function() { }); it('should only return the payload from the response object', function() { }); }); });
  • 25.
    The { Implementation} function userService($http, baseUrl) { baseUrl = baseUrl || 'http://google.com/api'; function composeFullName(firstName, lastName) { return firstName + ' ' + lastName; } function returnPayload(response) { return response.payload; } function execute(path, body, method) { return $http({ url: baseUrl + path, method: method || 'GET', data: body }); } return { saveUser: function(user) { user.fullName = composeFullName(user.firstName, user.lastName); return execute('/user', user, 'POST').then(returnPayload); } }; }
  • 26.
    Implement { OurUnit Tests } describe('user service', function() { var userService, httpMock, thenFunc; function createHttpMock() { thenFunc = sinon.stub(); httpMock = sinon.stub().returns({ then: thenFunc }); } beforeEach(function() { createHttpMock(); userService = UserService(httpMock); }); function getUser() { return { firstName: 'Ran', lastName: 'Mizrahi' }; }
  • 27.
    Implement { OurUnit Tests } describe('#saveUser()', function() { var user; beforeEach(function() { user = getUser(); userService.saveUser(user); }); it('should call http service with method POST, path /user, and the user object', function() { expect(httpMock).to.have.been.calledWith({ url: 'http://google.com/api/user', method: 'POST', data: user }); }); it('should compose the full name in to the user object', function() { expect(user.fullName).to.equal('Ran Mizrahi'); }); it('should only return the payload from the response object', function() { var returnPayload = thenFunc.args[0][0]; expect(returnPayload({ payload: 'Hello!!!' })).to.equal('Hello!!!'); }); }); });
  • 28.
    Run Those {Tests Again }
  • 29.
    Running The {Tests } mocha tests can run in different environments and formats: • Browser - using mocha.js (see example) • For CI automation use JSTestDriver. • CLI - as demonstrated before using the “mocha” command. • CI (e.g. xunit) - $ mocha test/asyncTest.js --reporter xunit. • Many other formats (JSON, HTML, list, Spec, etc.)
  • 30.
    Benefits of {Testing The Code } • Short feedback/testing cycle. • High code coverage of tests that can be at run any time to provide feedback that the software is functioning. • Provides detailed spec/docs of the application. • Less time spent on debugging and refactoring. • Know what breaks early on. • Enforces code quality and simplicity. • Helps separating units to responsibilities.
  • 31.