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


Command line


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



  1. Remote debugging
  2. Attach to test runners

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

  1. Define Purpose
  2. Static Code Analysis
  3. Make code callable 
  4. Write tests
  5. Mock
  6. Automate


Maintainable, "safe", secure
More Agile


the end

questions ???

Reference links

         TestRunners              Tools               Frameworks
         JsTestDriver           GruntJS           Jasmine
                  BusterJS           Yeoman           Mocha
                      Testem        PhantomJS       QUnit
                             Yeti           CasperJS         Tutti
                                            JSLint / JSHint







Articles

JAVASCRIPT TESTING

By Tobias Lundin

JAVASCRIPT TESTING

An introduction to getting ready for front end testing, concepts, automation's and tools.

  • 2,107