Testing

JavaScript

Terminology

  • Unit tests
  • Integration tests
  • End to end tests (Functional tests)
  • TDD
  • BDD

TDD ?

Test Driven Development

Test first

TDD is when your development is test first.

In other words, it's when you write tests and make them fail first, then start implementing them in your code to make them pass, then you can refactor safely.

// Calc.js

// A super Calculator
function Calc(opts) {
    var initialValue = opts.initialValue || 0;
    var value = initialValue;























}
// Calc.test.js

// TDD
suite('Calc') {
    var calc = new Calc();

    





    







    


}
    test('add a num') {
        calc.add(2);
    
        assert.equal(calc.getValue(), 2);
    }
    test('subtract a num') {
        calc.subtract(1);
    
        assert.equal(calc.getValue(), 1);
    }
    test('clear the value') {
        calc.clear();
    
        assert.equal(calc.getValue(), 0);
    }
    this.add = function (num) {
        value += num;
        return;
    };
    this.subtract = function (num) {
        value -= num;
        return;
    };
    this.getInitialValue = function () {
        return initialValue;
    };
    this.getValue = function () {
        return value;
    };
    this.reset = function reset() {
        value = initialValue;
        return;
    };

BDD ?

Behavior Driven Development

Still a TDD, but...

BDD is an approach to tests that solves some problems that might occur with TDD, it means to test against scenarios (behavior) instead of implementations.

// Calc.test.js

// BDD
describe('Calc') {
    var calc;

    beforeEach() {
        calc = new Calc({
            initialValue: 2,
        });
    }

    it('should add a num') {
        calc.add(2);
    
        expect(calc.getValue()).to.equal(calc.getInitialValue() + 2);
    }

    it('should subtract a num') {
        calc.subtract(1);
   
        expect(calc.getValue()).to.equal(calc.getInitialValue() - 1);
    }

    it('should clear the value') {
        calc.add(1);
        calc.clear();
    
        expect(calc.getValue()).to.equal(calc.getInitialValue());
    }
}

Tools

  • Runner
  • Framework
  • Assertion Library
  • Mocking/Spying Library

Test Runner

Framework

// Mocha Framework
describe('Array', function() {
    before(function() {
      // ...
    });
    
    describe('#indexOf()', function() {
        context('when not present', function() {
            it('should not throw an error', function() {
              // assertions
            });
    
            it('should return -1', function() {
                // assertions
            });
        });

        context('when present', function() {
            it('should return the index of the element', function() {
                // assertions
            });
        });
    });
});

Assertions

// Mocha Framework
describe('Array', function() {
    before(function() {
      // ...
    });
    
    describe('#indexOf()', function() {
        context('when not present', function() {
            it('should not throw an error', function() {





            });
    
            it('should return -1', function() {



            });
        });
    });
});
                (function() {
                    [1,2,3].indexOf(4);
                }).should.not.throw();
                [1,2,3].indexOf(4).should.equal(-1);

Runners

Mocha Karma Jest AVA
AIO fw no yes yes
EOC simple a little bit cheesy very easy n/a
DOCS website
not bad
website
good
website
excellent
github
excellent

- AIO (All in One): does the runner provide also a framework and assertions...etc

- EOC (Ease of Configuration): how easy is it to be configured.

- DOCS: how is the documentation, is it easily accessible.

Framewords

Mocha Jasmine Tape QUnit
AIO no yes asserts yes
EOC simple simple n/a n/a
DOCS website
not bad
website
good
github
not bad
website
very good
BDD yes yes no no

- AIO (All in One): does the framework provides other features, assertions...etc

- EOC (Ease of Configuration): how easy is it to be configured.

- DOCS: how is the documentation, is it easily accessible.

Assertion Libs

Chai Should Expect Must
DOCS website
excellent
website
excellent
github
not bad
github
good
BDD yes yes yes yes
ASYNC plugins yes yes yes

- DOCS: how is the documentation, is it easily accessible.-

- ASYNC: does the library support assertions on promises, async code.

Chai

// chai should
chai.should();

foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.lengthOf(3);
tea.should.have.property('flavors').with.lengthOf(3);


// chai expect
var expect = chai.expect;

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.lengthOf(3);
expect(tea).to.have.property('flavors').with.lengthOf(3);

// chai assert
var assert = chai.assert;

assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.lengthOf(foo, 3)
assert.property(tea, 'flavors');
assert.lengthOf(tea.flavors, 3);

Should

(5).should.be.exactly(5).and.be.a.Number();
should(5).be.exactly(5).and.be.a.Number();
(10).should.not.be.a.Promise();

Expect

expect(1).to.be.ok();
expect({ a: 'b' }).to.have.key('a');
expect([]).to.be.empty();
expect(1).to.eql('1');
expect(program.version).to.match(/[0-9]+\.[0-9]+\.[0-9]+/);

Must

[].must.be.empty();
{}.must.have.nonenumerable("foo");
(42).must.be.above(13);

Promise.resolve(42).must.then.equal(42);
Promise.resolve([1, 2, 3]).must.eventually.not.include(42);
Promise.reject(new Error("Problemo")).must.reject.with.error(/problem/i);

Mocks/Spys

Sinon Test double Mockery jsMock
DOCS website
excellent
github
good
github
not bad
github
bad
FF yes yes no no

- DOCS: how is the documentation, is it easily accessible.-

- FF: is it full featured ? mocks, spies, stubs ...etc.

Spies

// Spy
function callOnce(fn) {
    var called = false;
    return function() {
        if (!called) fn.apply(this);
        called = true;
    };
}



    





it('calls the passed function', function () {
    var callback = sinon.spy();
    var proxy = callOnce(callback);
    
    proxy();

    assert(callback.called); // pass
});
it('calls the passed function once', function () {
    var callback = sinon.spy();
    var proxy = callOnce(callback);
    
    proxy();
    proxy();

    assert(callback.calledOnce); // pass
});

Stubs

// Ajax
function getTodoItems(id, callback) {
    jQuery.ajax({
        url: '/todo/' + id + '/items',
        success: function (data) {
            callback(data);
        }
    });
}
it('makes a GET request for todo items', function () {
    sinon.stub(jQuery, 'ajax');
    getTodoItems(42, sinon.spy());

    assert(jQuery.ajax.calledWithMatch({ url: '/todo/42/items' })); // pass
});

Timers

// Ajax
function defer(fn) {
    var timeout;
    return function () {
        clearTimeout(timeout);
        timeout = setTimeout(fn, 100);
    };
}

it('it calls the passed function only after 100ms', function () {
    var clock = sinon.useFakeTimers();
    var fn = sinon.spy();
    var proxy = defer(fn);

    proxy();
    clock.tick(99);

    assert(fn.notCalled); // pass

    clock.tick(100);

    assert(fn.calledOnce); // pass
});

My Choices

Easy Option

  • Jest

All in one, simple, easy to config and it's backed and used by folks at Facebook.

Advanced Option

  • Mocka
  • Chai
  • Sinon

Have more features, yet still simple and they are very popular.

Another Nice Option

  • Karma
  • Jasmine
  • ... more if needed

This is a nice combination to consider as well, I've tested it with AngularJS and I was ok with it.

Thanks

Testing JavaScript

By Mohammed Erraysy

Testing JavaScript

An Introduction to Code Testing with a quick overview of some of the available JavaScript testing tools

  • 261