Upload
javascript-meetup-hcmc
View
438
Download
3
Tags:
Embed Size (px)
DESCRIPTION
About us Author: Ted Piotrowski Find me at: [email protected] 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
Citation preview
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
More information
● Testable Javascript● Karma test runner● Sinon.js● Istanbul
About usAuthor: Ted PiotrowskiFind me at: [email protected] 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