testing javascript in the frontend

57
Testing Javascript from a Frontend perspective Frederic Cabassut - Campanda

Upload: frederic-cabassut

Post on 13-Apr-2017

100 views

Category:

Software


0 download

TRANSCRIPT

Testing Javascript from a Frontend perspective

Frederic Cabassut - Campanda

The basics of test-driven development

TDD Cycle - Rule #1

You must write a failing test

before you write any production

code.

TDD Cycle - Rule #2

You must not write more of a

test than is sufficient to fail, or

fail to compile.

TDD Cycle - Rule #3

You must not write more

production code than is

sufficient to make the currently

failing test pass.

TDD Cycle// tests // production code

TDD Cycle// tests

var expected = "Hello, Fred!";

// production code

TDD Cycle// tests

var expected = "Hello, Fred!";

// production code

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

// production code

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

// production code

Uncaught ReferenceError: getMyObj is not defined

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

// production code

function getMyObj() {

}

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

// production code

function getMyObj() {

}

Uncaught TypeError: Cannot read property 'greet' of undefined

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

// production code

function getMyObj() {

return {};

}

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

// production code

function getMyObj() {

return {};

}

Uncaught TypeError: getMyObj(...).greet is not a function

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

// production code

function getMyObj() {

return {

greet: function() {

}

};

}

}

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

// production code

function getMyObj() {

return {

greet: function () {

}

};

}

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

assert.equal(expected, actual);

// production code

function getMyObj() {

return {

greet: function() {

}

};

}

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

assert.equal(expected, actual);

// production code

function getMyObj() {

return {

greet: function() {

}

};

}

AssertionError: "Hello, Fred!" == undefined

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

assert.equal(expected, actual);

// production code

function getMyObj() {

return {

greet: function() {

return "Hello, Fred!";

}

};

}

TDD Cycle// tests

var expected = "Hello, Fred!";

var actual = getMyObj().greet("Fred");

assert.equal(expected, actual);

// production code

function getMyObj() {

return {

greet: function() {

return "Hello, Fred!";

}

};

}

Why Testing ?- Increase code quality

- Deploy with confidence

- Self-explaining documentation

- Easy to refactor

What do we have to test ?- User interaction / events

- Manipulating the DOM

- Client-side business logic

- Ajax request (retrieve/send data)

My favorite testing stack:- Mochajs https://mochajs.org/

- Chaijs http://chaijs.com/

- Sinonjs: http://sinonjs.org/

- My browser (+ livereload)

Let’s create a DatePicker!

Let’s create a DatePicker!

Let’s create a DatePicker!

Let’s create a DatePicker!

Let’s create a DatePicker!

User interaction / events// tests

it('should initialize a datepicker with

a given element', function() {

var input =

document.createElement('input');

DatePicker.init(input);

});

// production code

window.DatePicker = {

init: function() {}

}

User interaction / events// tests

it('should initialize a datepicker with

a given element', function() {

var input =

document.createElement('input');

DatePicker.init(input);

});

// production code

window.DatePicker = {

init: function() {}

}

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

});

// production code

window.DatePicker = {

init: function() {}

}

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

var input =

document.createElement('input');

DatePicker.init(input);

});

// production code

window.DatePicker = {

init: function() {}

}

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

var input =

document.createElement('input');

DatePicker.init(input);

});

// production code

window.DatePicker = {

init: function() {}

}

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

[...]

});

// production code

window.DatePicker = {

init: function() {}

}

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

[...]

var event = new Event('focus');

input.dispatchEvent(event);

});

// production code

window.DatePicker = {

init: function() {}

}

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

[...]

var event = new Event('focus');

input.dispatchEvent(event);

});

// production code

window.DatePicker = {

init: function() {}

}

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

[...]

var event = new Event('focus');

input.dispatchEvent(event);

var element =

document.getElementById('datePicker');

expect(element).to.be.ok;

});

// production code

window.DatePicker = {

init: function() {}

}

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

[...]

var event = new Event('focus');

input.dispatchEvent(event);

var element =

document.getElementById('datePicker');

expect(element).to.be.ok;

});

// production code

window.DatePicker = {

init: function() {}

}

AssertionError: expected null to be truthy

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

[...]

var event = new Event('focus');

input.dispatchEvent(event);

var element =

document.getElementById('datePicker');

expect(element).to.be.ok;

});

// production code

window.DatePicker = {

init: function(input) {

[...]

}

}

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

[...]

var event = new Event('focus');

input.dispatchEvent(event);

var element =

document.getElementById('datePicker');

expect(element).to.be.ok;

});

// production code

[...]

input.addEventListener('focus',

function() {

var element =

document.createElement('div');

element.id = 'datePicker';

document.body.appendChild(element);

}

[...]

User interaction / events// tests

it('should show a datepicker on input

focus', function() {

[...]

var event = new Event('focus');

input.dispatchEvent(event);

var element =

document.getElementById('datePicker');

expect(element).to.be.ok;

});

// production code

[...]

input.addEventListener('focus',

function() {

var element =

document.createElement('div');

element.id = 'datePicker';

document.body.appendChild(element);

}

[...]

User interaction / events// tests

it('should stop event propagation',

function() {

});

// production code

[...]

input.addEventListener('focus',

function() {

var element =

document.createElement('div');

element.id = 'datePicker';

document.body.appendChild(element);

}

[...]

User interaction / events// tests

it('should stop event propagation',

function() {

[...]

var event = new Event('focus');

var spy = sinon.spy(event,

'stopPropagation');

input.dispatchEvent(event);

expect(spy).to.have.been.calledOnce;

});

// production code

[...]

input.addEventListener('focus',

function() {

var element =

document.createElement('div');

element.id = 'datePicker';

document.body.appendChild(element);

}

[...]

User interaction / events// tests

it('should stop event propagation',

function() {

[...]

var event = new Event('focus');

var spy = sinon.spy(event,

'stopPropagation');

input.dispatchEvent(event);

expect(spy).to.have.been.calledOnce;

});

// production code

[...]

input.addEventListener('focus',

function() {

var element =

document.createElement('div');

element.id = 'datePicker';

document.body.appendChild(element);

}

[...]

expected stopPropagation to have been called exactly once ...

User interaction / events// tests

it('should stop event propagation',

function() {

[...]

var event = new Event('focus');

var spy = sinon.spy(event,

'stopPropagation');

input.dispatchEvent(event);

expect(spy).to.have.been.calledOnce;

});

// production code

[...]

input.addEventListener('focus',

function(e) {

e.stopPropagation();

var element =

document.createElement('div');

element.id = 'datePicker';

document.body.appendChild(element);

}

[...]

User interaction / events// tests

it('should stop event propagation',

function() {

[...]

var event = new Event('focus');

var spy = sinon.spy(event,

'stopPropagation');

input.dispatchEvent(event);

expect(spy).to.have.been.calledOnce;

});

// production code

[...]

input.addEventListener('focus',

function(e) {

e.stopPropagation();

var element =

document.createElement('div');

element.id = 'datePicker';

document.body.appendChild(element);

}

[...]

Manipulating the DOM// tests

it('should add class "selected" on a day when it is clicked',

function(){

});

Manipulating the DOM// tests

it('should add class "selected" on a day when it is clicked',

function(){

[...]

var day = document.querySelector('#datePicker .day');

day.click();

});

Manipulating the DOM// tests

it('adds class "selected" on a day when clicked', function() {

[...]

var day = document.querySelector('#datePicker .day');

day.click();

var i = day.className.indexOf('selected');

expect(i > -1).to.be.true;

});

Mocking Time, tick tick tick// tests

Mocking Time// tests

it('should hide datePicker after 300ms', function(){

[...]

var clock = sinon.useFakeTimers();

day.click();

clock.tick(299);

expect(datePicker.className.indexOf('hidden') > -1).to.be.false;

clock.tick(2);

expect(datePicker.className.indexOf('hidden') > -1).to.be.true;

clock.restore();

});

Mocking Browser methods// tests

Mocking Browser methods// tests

beforeEach(function() {

this.dateNowStub = sinon.stub(Date, 'now', function() {

return new Date('2016-01-13').getTime();

});

});

afterEach(function(){

this.dateNowStub.restore();

});

Testing Javascript for the BrowserIn a browser

Take it to the next levelHeadless browser testing (phantomjs, zombie, electron...)

Continuous Integration: automate your tests in jenkins, travis…

A few more tips- Find a BUG ? Write a TEST!

- Do TDD while PAIRING

- Use the Web APIs in your TESTS

- Don’t try to test the browser

- You don’t need to test libraries

THANK YOUAnd HAPPY Testing!