javascript testing
An introduction
Tobias Lundin
tobias.lundin@gmail.com
tobias.lundin@netlight.no
Javascript .NET TDD BDD Processes
Skiing Movies Series Books
tv.nrk.no

JsTestDriver GruntJS
PhantomJS CasparJS
#1 First things first
Define your purpose
Different goals, different tests
- Usability testing
- Performance testing
- Consistency/Regression testing
i.e. Functional testing
!!MAINTANABLE CODE!!
the important parts
Do not test browser incompatibilites
What errors do you usually do?
Test where testing matters!
Working QA process
#2 First line of defense
Static Code Analysis
JSLint/Hint
-
catch your typos
- code rule compliance
Online
Plugins available for most IDE's
#3 Make code callable
Avoid anti-patterns
- Inline javascript
- Inaccessible code, no public interface
- Missing constructor/prototype
- Pyramid of doom - nested structures
- Notation-less asynchronous logic
a better idea
The Module Pattern
var MODULE = (function () {
var privateVariable = 1;
function privateMethod() {
// ...
}
function publicMain () {
// ...
};
return {
main : publicMain
};
}());
Organize jQuery code in Plugins
(function($){
var prototype = {
init : function(){ ... }
};
$.fn.myPlugin = function(params){
var obj = Object.create(prototype);
return this.each(function(){
obj.init();
});
};
}(jQuery));
#4 write Unit tests
reproducible errors
there are no bugs, just unwritten specifications
A test IS a specification
a basic Framework
// the assertion
function assert(value, message){
return !!value ? 'PASS!' : 'Fail: ' + message;
}
//the method under test
function add(a,b){
return a+b;
}
//the test
var result = (add(38,4) === 42);
assert(result, "should return Mr Adams favorite number");
test in browser console
using QUnit
(function($){
module('moduleUnderTest', {
setup: function(){
// handle setup logic
}
});
test('returns sum of values passed in', 1, function(){
deepEqual(add(38,4), 42, 'should return 42');
});
}(jQuery));
test by visiting QUnit html page
pass!

#5 mock for your life
find things hard to test?
have a lot of ajax and timeouts?
cometh sinonjs
Provides
spy / stub / mock / clock / server
spies
"test should call subscribers on publish": function () {
var callback = sinon.spy();
PubSub.subscribe("message", callback);
PubSub.publishSync("message");
assertTrue(callback.called);
}
Alternative
"test should call ajax twice": function () {
sinon.spy(jQuery, "ajax");
jQuery.getJSON('/some/resource');
jQuery.getJson('/some/other/resource');
sinon.assert.calledTwice(jQuery.ajax);
}
stubs
"test stub differently based on arguments": function () {
var callback = sinon.stub();
callback.withArgs(42).returns(1);
callback.withArgs(1).throws("TypeError");
callback(42); // Returns 1
callback(1); // Throws TypeError
}
"test useful for stubbing externals": function(){
var deferred = $.Deferred();
sinon.stub(jQuery, "ajax").returns(deferred.promise);
someMethodThatUsesAjax();
deferred.resolve();
}
fake timers
{
setUp: function(){
this.clock = sinon.useFakeTimers();
},
tearDown: function(){
this.clock.restore();
},
"test should animate el over 500ms": function () {
var el = jQuery('<div>');
el.appendTo(document.body);
el.animate({height: '200px'});
this.clock.tick(510);
assertEquals('200px', el.css('height'));
}
}
much more
// fake servers
var server = sinon.fakeServer.create(),
headers = { "Content-Type": "application/json" },
response = '[{ "value" : "heeeey!" }]';
server.respondWith("GET", "/some/resource",
[200, headers, response]);
// call method that makes call to resource
getComments();
// tell server to respond
server.respond();
// Assertions...
Also: assertions, matchers, sandboxes etc
#6 automation
Get yourself a Test Runner
CI-Integration
- JsTestDriver
- BusterJS
- (Grunt)
- Testem
- Yeti
testing mobile devices
remote debugging
Tools
capture browsers
JsTestDriver BusterJS Testem


All test frameworks can be run via browser page
(or using PhantomJs)
an example
Using QUnit with GruntJS and
PhantomJS to create a jQuery plugin



The plugin
$.fn.replaceTag
Should replace tag names
while preserving all attributes
and child nodes
Giving grunt the go
Installing grunt
// first install node from nodejs.org
> npm install -g grunt
Initialize grunt-file
> grunt init:jquery
Please answer the following:
[?] Project name (replaceTag)
[?] Project title (ReplaceTag)
[?] Description (The best jQuery plugin ever.)
[?] Version (0.1.0)
... etc ...
Note: PhantomJS needs to be in path to run qunit
Folder structure

the grunt file
module.exports = function(grunt){
grunt.initConfig({
concat: { dist: { src: [''], dest: [''] } }, //min
qunit: {
files: ['test/**/*.html']
},
lint: {
files: ['src/**/*.js', 'test/**/*.js']
},
watch: {
files: '<config:lint.files>',
tasks: 'lint qunit'
},
jshint: {
options: { ... },
globals: { ... }
}
});
grunt.registerTask('default', 'lint qunit concat min');
}
running grunt
// On Windows you need to run grunt.cmd
> grunt
// automatically runs default task ('lint qunit concat min')

// Now lets try the watch command
> grunt watch

Recap
-
Define Purpose
-
Static Code Analysis
-
Make code callable
-
Write tests
-
Mock
-
Automate
Maintainable, "safe", secure
More Agile
the end
questions ???
Reference links
TestRunners Tools Frameworks
Articles
- Unit Testin g 101 - C. Johansen
- How to Test your code with QUnit
- Testing JS with PhantomJS
- Make JS Testing Fun With Testem
- T esting like the TSA
- Testing Your JS with Jasmine
- Intro to JS Testing - J. Zaefferer
- Introducing Yeti: The YUI Easy Testing Interface
- JS Module Pattern In Depth
- Meet Grunt: The Build Tool for JS
- Test-Driven JS Development in Practice - C. Johansen
- Testing jQuery Plugins Cross Version With Grunt
- Testable Frontend JS Part 1 - Anti Patterns
- Mocks Aren't Stubs
JAVASCRIPT TESTING
By Tobias Lundin
JAVASCRIPT TESTING
An introduction to getting ready for front end testing, concepts, automation's and tools.
- 2,241