Don't forget to cover your client side!


Presented by Jonathan Creamer at the 2014 JavaScript Summit.
@jcreamer898
slides

Legacy code is code without tests.

-Michael Feathers, Working Effectively with Legacy Code

What is a “unit test”

unit - An individual thing or person regarded as a single and complete but which can also form an individual component of a larger or more complex whole.

test - A procedure intended to establish the quality performance, or reliability of something, especially before it is taken into widespread use.

A unit test therefore is…

A method by which we determine if a given “unit” of code behaves as expected by asserting it’s results given certain parameters and conditions.

Prevent Bugs

  • helps release LESS buggy code
  • you have tests to prove stuff works
  • Bug record keeping
  • Now vs later

If you don’t do it, someone else will.

Code Design

  • Designing the API
  • Forces you to consider different edge cases
  • Makes adding new features without breaking things easier
  • Creates living breathing documentation

 If code is tested, then it is typically understandable and maintainable because it has been written to be tested

-Cody Lindley

Maintainable features === testable features

  • The results of maintainable code are often the same as the results of testable code
  • Small functions
  • Easy to read
  • Documented
  • De-coupled
  • Re-usable

Types of Testing

Unit, BDD, TDD, Integration, etc etc.
  • There are many different types
  • Unit tests
  • Test First
  • TDD - Test Driven Development
  • BDD - Behavior Driven Development
  • Integration
  • Regression

TDD

  • The solution needs to be the simplest solution
  • Be sure to isolate to a unit
  • Don’t write “future code” write “make it pass” code
  • Improve the design of the API
  • Makes refactoring easier
  • Helps reduce coupling

Client Side Testing

  • JavaScript applications are much much larger
  • Compile at run time
  • Test offline
  • Can test outside of the browser

Excuses, excuses…

  • Client side unit testing presents different challenges than server side testing.
  • That whole DOM thing
  • It’s tempting to just “reload develop”
  • “More code”
  • “More work”

Getting Started

  • Abstract the DOM
  • Use a framework/tool like angular, backbone, or knockout
  • Pick a test library
  • Pick a mock library
  • TDD a bug or three

QUnit

  • QUnit is a simple testing library for JavaScript
  • Written by the jQuery team
  • Simple setup and assertions
  • Low barrier to entry

QUnit First Steps

Easy setup
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>QUnit Example</title>
  <link rel="stylesheet" href="//code.jquery.com/qunit/qunit-1.14.0.css">
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="//code.jquery.com/qunit/qunit-1.14.0.js"></script>
  <script src="yourCode.js"></script>
  <script src="tests.js"></script>
</body>
</html>

 module

module('test stuff', {
    setup: function() {
        // test
    },
    teardown: function() {
        // after
    }
});

Assertions

equal(actual, expected, [message]);
function add(x, y) {
    return x + y;
}
test("equal", function() {
    var sum = 4;
    equal(add(2, 2), sum, "these two numbers are equal");
});
test("loose equal", function() {
    equal(2, "2");
});
test('equality', function() {
    var obj = { foo: 'bar' };
    equal(obj, { foo: 'bar'});
});
deepEqual(actual, expected, [message]);
test('deep equality', function() {
    var obj = { foo: 'bar' };
    deepEqual(obj, { foo: 'bar'});
});
ok(actual, [message]);
function isNumber(val) {
    return !isNaN(Number(val));
}

test('isNumber', function() {
    var num = 7;
    ok(isNumber(num));
});
throws(block, expected, [message]);
var doIt = function(x) {
    if (!x) {
        throw "hey now!";
    }
};

test('throws', function() {
    throws(function() {
        doIt();
    }, "hey now!");
});
 propEqual(actual, expected, [message]);
function Foo(bar) {
    this.bar = bar;
}

test('propEqual', function() {
    var foo = new Foo(1);
    propEqual(foo, { bar: 1 });
});

 Assertions

  • Every assertion has a “not”
  • They aren’t used very often though
  • notOk, notEqual, etc…

Async

asyncTest('setTimeout', function() {
    function fooAsync() {
        var dfd = $.Deferred();
        
        setTimeout(function() {
            dfd.resolve([1,2,3]);
        }, 0);
        
        return dfd.promise();
    }
    
    fooAsync().done(function(arr) {
        equal(arr.length, 3);
        start();
    });
});

Unit tests 100% pass, code still crashes!

Mocking with Sinon.js

  • In every unit test, there should be one unit under test
  • Unit tests need to be as isolated as possible
  • Mocking helps remove dependencies
  • Spy
  • Stub
  • Mock
  • Other stuff too…

Spy

  • A function that either wraps an existing function or is anonymous and records arguments, return value, exceptions
test('spy', function(){ 
    var spy = sinon.spy();

    var $div = $("<div />").on("click", spy);

    $div.trigger("click");

    ok(spy.calledOnce);
});

 Stub

  • Functions (spies) with pre-programmed behavior.
test('stub', function(){ 
    function ajax(options) {
        return $.ajax(options)
    }

    sinon.stub(jQuery, "ajax").returns({
        done: $.noop
    });

    ajax({ url: "/foo" });

    ok(jQuery.ajax.calledOnce);

    jQuery.ajax.restore();
});

 Stub

test('stub', function(){ 
    var callback = sinon.stub();

    callback.onCall(0).returns(1);
    callback.onCall(1).returns(2);
    callback.returns(3);

    equal(callback(), 1);
    equal(callback(), 2);
    equal(callback(), 3);

});

Mock

  • Combo of a spy and stub with preprogrammed expectations
test('mock', function(){ 
    var listeners = {
        click: function(){}
    };
    var mock = sinon.mock(listeners);
    mock.expects("click").once();

    var $div = $("<div />").on("click", listeners.click);

    $div.trigger("click");

    ok(mock.verify());
});

 Test Coverage

  • Analyses which parts are tested
  • Blanket, JSCover, Istanbul…
  • Set a metric
  • May or not be accurate, but it helps!

 Plato

Automate automate automate

Grunt FTW

  • Grunt is a node.js based task runner

  • Huge community support

  • Config file based setup

  • Can automate many things

  • Tests, concat, uglify, etc

  • Easy setup

  • Install node

  • Setup a package.json

  • Use npm to install grunt

npm install -g grunt-cli
cd project-directory
npm init
npm install grunt --save-dev
npm install grunt-contrib-qunit --save-dev

Live Demo

Check it out at http://jcreamer898.github.io/cover-your-client-side/