31
Introduction to { JavaScript Testing } Ran Mizrahi (@ranm8) Founder and CEO @ CoCycles

Intro To JavaScript Unit Testing - Ran Mizrahi

Embed Size (px)

Citation preview

Page 1: Intro To JavaScript Unit Testing - Ran Mizrahi

Introduction to { JavaScript Testing }

Ran Mizrahi (@ranm8)Founder and CEO @ CoCycles

Page 2: Intro To JavaScript Unit Testing - Ran Mizrahi

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.

Page 3: Intro To JavaScript Unit Testing - Ran Mizrahi

• Deliver late or over budget.

• Deliver the wrong thing.

• Unstable in production.

Production Maintenance

• Expensive maintenance.

• Long adjustment to market needs.

• Long development cycles.

Why Do Software Projects { Fail } ?

Page 4: Intro To JavaScript Unit Testing - Ran Mizrahi

Why Do Software Projects { Fail } ?

Page 5: Intro To JavaScript Unit Testing - Ran Mizrahi

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); }); }); }

Untestable { Code }…

Page 6: Intro To JavaScript Unit Testing - Ran Mizrahi

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 } ?

Page 7: Intro To JavaScript Unit Testing - Ran Mizrahi

Methodology for using automated unit tests to drive software design, quality

and stability.

{ Test-Driven Development } To The Rescue

Page 8: Intro To JavaScript Unit Testing - Ran Mizrahi

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.

{ Test-Driven Development } To The Rescue

Page 9: Intro To JavaScript Unit Testing - Ran Mizrahi

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.

Seems Great But How Much Longer Does { TDD Take } ?

Page 10: Intro To JavaScript Unit Testing - Ran Mizrahi

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.

The { Three Rules } of TDD

Page 11: Intro To JavaScript Unit Testing - Ran Mizrahi

Test-Driven Development

What exactly are we testing?!

{ BDD } Behaviour-Driven Development

Page 12: Intro To JavaScript Unit Testing - Ran Mizrahi

• 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

Page 13: Intro To JavaScript Unit Testing - Ran Mizrahi

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 """

{ BDD } Behaviour-Driven Development

Page 14: Intro To JavaScript Unit Testing - Ran Mizrahi

• Unit Testing

• Integration Testing

• Functional Testing

Main { Test Types }

Page 15: Intro To JavaScript Unit Testing - Ran Mizrahi

• 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.)

{ Challenges } Testing JavaScript

Page 16: Intro To JavaScript Unit Testing - Ran Mizrahi

Mocha is a feature-rich JavaScript test frameworks running on node and the browser, making asynchronies tests easy.

Mocha

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 }

Page 17: Intro To JavaScript Unit Testing - Ran Mizrahi

Chai is a BDD / TDD assertion library for node and the browser that can be paired with any JavaScript testing framework.

ChaiJS

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 }

Page 18: Intro To JavaScript Unit Testing - Ran Mizrahi

Installing Mocha and Chai

$ npm install mocha -g

$ npm install chai

Install mocha globally using npm:

Install Chai (Locally):

TDD/BDD Using { Mocha and ChaiJS }

Page 19: Intro To JavaScript Unit Testing - Ran Mizrahi

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); }); }); });

“Normal” test:

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 }

Page 20: Intro To JavaScript Unit Testing - Ran Mizrahi

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...

TDD/BDD Using { Mocha and ChaiJS }

Page 21: Intro To JavaScript Unit Testing - Ran Mizrahi

Back To { Our Code }

Page 22: Intro To JavaScript Unit Testing - Ran Mizrahi

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 }

Page 23: Intro To JavaScript Unit Testing - Ran Mizrahi

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 Tests }

Page 24: Intro To JavaScript Unit Testing - Ran Mizrahi

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() { }); }); });

Page 25: Intro To JavaScript Unit Testing - Ran Mizrahi

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); } }; }

Page 26: Intro To JavaScript Unit Testing - Ran Mizrahi

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' }; }

Page 27: Intro To JavaScript Unit Testing - Ran Mizrahi

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!!!'); }); }); });

Implement { Our Unit Tests }

Page 28: Intro To JavaScript Unit Testing - Ran Mizrahi

Run Those { Tests Again }

Page 29: Intro To JavaScript Unit Testing - Ran Mizrahi

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.)

Running The { Tests }

Page 30: Intro To JavaScript Unit Testing - Ran Mizrahi

• 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.

Benefits of { Testing The Code }

Page 31: Intro To JavaScript Unit Testing - Ran Mizrahi

Questions?Thank you!