writing testable js [by ted piotrowski]

Post on 22-May-2015

449 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

DESCRIPTION

About us Author: Ted Piotrowski Find me at: tpiotrowski@atlassian.com Sample code: https://bitbucket.org/tpiotrowski/js-hcm Presentation made for Javascript Ho Chi Minh City Meetup Group You can find us at: http://www.meetup.com/JavaScript-Ho-Chi-Minh-City/ https://www.facebook.com/JavaScriptHCMC https://plus.google.com/u/0/communities/116105314977285194967

TRANSCRIPT

Writing testable JSby Ted Piotrowski

Javascript Ho Chi Minh City

Excuses for not testing

● I know the code● Test suite is hard to configure and run● You can’t test UI● You can’t unit test JS code

$(document).ready(function() {

$('#new-status form').submit(function(e) {

e.preventDefault();

$.ajax({

url: '/status',

type: 'POST',

dataType: 'json',

data: { text: $('#new-status').find('textarea').val() },

success: function(data) {

$('#statuses').append('<li>' + data.text + '</li>');

$('#new-status').find('textarea').val('');

}

});

});

});

source: https://github.com/kjbekkelund/writings/blob/master/published/understanding-backbone.md/

Documentation

● Impossible to write a good test without a good specification

● If you don’t have time to write a test, at least write documentation○ it will allow others to write tests later

/**

* adds two numbers together

*/

function sum(a, b)

assert(sum(1, 2), 3);

/**

* adds two numbers together

*/

function sum(a, b)

assert(sum(1, ‘a’), ?);

/**

* adds two numbers together,

* otherwise returns null

*/

function sum(a, b)

assert(sum(1, ‘a’), null);

Dependencies

● Can’t write good tests unless you understand what external objects the code depends on

● loose coupling● use requirejs, almond.js, squire.js● move dependencies up the call stack and

inject

Scope

● An assertion should only rely on the method being tested

● What is a “unit”?● is $(function() { }) a unit?

// Should we stub addTwo, addOne?

// When we limit scope, we forfeit integration

function addThree(a) {

var x = addTwo(a);

var y = addOne(x);

return y;

}

Testing with the DOM

● Use a DOM fragment / jQuery fragment● Inject it into the module constructor

function Word() {

this.el = $('.word'); // external dependency

}

Word.prototype.setText = function(text) {

this.el.text(text);

};

var word = new Word();

word.setText('Hello World');

assert(???, 'Hello World');

function Word(el) {

this.el = el; // dependency injection

}

Word.prototype.setText = function(text) {

this.el.text(text);

};

var mockEl = $('<div></div>'); // use a test double

var word = new Word(mockEl); // inject the dependency

word.setText('Hello World');

assert(mockEl.text(), 'Hello World');

function Word(el) {

this.el = el || $('.word'); // optional injection

}

Word.prototype.setText = function(text) {

this.el.text(text);

};

var mockEl = $('<div></div>');

var word = new Word(mockEl);

word.setText('Hello World');

assert(mockEl.text(), 'Hello World');

Dealing with window properties

● You code will likely touch Web API○ document, Math,

● Can use mocks and stubs for methods● Many Web API properties are read-only

// If your production code calls alert(), you can mock it

var spy = sinon.spy(window, "alert");

assert(spy.calledWith("My alert message"));

// However, you can't modify read only properties

Math.PI = 3.15; // won’t work

window.History.length = 12;

function Win(windowObj) {

this.window = windowObj || window;

}

Win.prototype.scrollX = function() { return this.window.scrollX };

Win.prototype.scrollY = function() { return this.window.scrollY };

var win = new Win(); // in production

// win.scrollX() => actual value

var win = new Win({ // in testing

scrollX: 50, scrollY: 40, History: { … }

}); // win.scrollX() => 50

Dealing with network calls

● Use sinon fake server

{

"test should fetch comments from server" : function () {

this.server.respondWith("GET", "/some/article/comments.json",

[200, { "Content-Type": "application/json" },

'[{ "id": 12, "comment": "Hey there" }]']);

var callback = sinon.spy();

myLib.getCommentsFor("/some/article", callback);

this.server.respond();

sinon.assert.calledWith(callback, [{ id: 12, comment: "Hey there" }]));

}

}

Code coverage

● Reports what lines of production JS are executed during testing

● necessary, but not sufficient● Istanbul is a good tool

○ integrates with Karma

JS integration tests

● main.js file initializes your components and injects DOM dependencies

● Stub your REST calls● Just like unit testing, user events are your

input, DOM changes are your output

// Bootstrap your application here

// Inject your DOM dependencies at top of call stack

// Allows you to mock/stub the DOM for each component

$(function() {

var $el = $('.some-element');

var component = new Component($el);

var $el2 = $('.some-element2');

var component2 = new Component($el2);

// initialize more components, global state here

});

Demo time

About usAuthor: Ted PiotrowskiFind me at: tpiotrowski@atlassian.comSample code: https://bitbucket.org/tpiotrowski/js-hcm

Presentation made for Javascript Ho Chi Minh City Meetup Group

You can find us at:● http://www.meetup.com/JavaScript-Ho-Chi-Minh-City/● https://www.facebook.com/JavaScriptHCMC● https://plus.google.com/u/0/communities/116105314977285194967● http://www.slideshare.net/JavascriptMeetup

Lean Coffee

Discussion topics

top related