Test Driven Development with JavaScript

Andrii Kucherenko

TDD steps

  • Navigation map

  • Test first

  • Assert first

  • Fail first

  • Continious integration

Test first

describe('Calculator', 
    function () {
        it('should summarize two numbers', 
            function () {
        
            }
        );
    }
);

Assert first

describe('Calculator', function () {

    it('should summarize two numbers', 
        function () {
            sut.sum(1, 2).should.equal(3);
        }
    );

});

Fail first

> mocha test/*.spec.js


  ․

  0 passing (4ms)
  1 failing

  1) String Calculator "before each" hook:
     ReferenceError: Calculator is not defined
      at Context.<anonymous> (/home/apk/workspace/lab/tdd/test/Calculator.spec.js:5:15)
      at callFn (/home/apk/workspace/lab/tdd/node_modules/mocha/lib/runnable.js:223:21)
      at Hook.Runnable.run (/home/apk/workspace/lab/tdd/node_modules/mocha/lib/runnable.js:216:7)
      at next (/home/apk/workspace/lab/tdd/node_modules/mocha/lib/runner.js:258:10)
      at Object._onImmediate (/home/apk/workspace/lab/tdd/node_modules/mocha/lib/runner.js:275:5)
      at processImmediate [as _immediateCallback] (timers.js:330:15)

Continious integration

Benefits

  • Unit tests coverage

  • Increases assurance of correctness

  • More complete explorations of requirements

  • Improved code

  • Code as documentation

  • Safe refactoring

  • No extra code

Baby steps 

Pair programming

Emergent Design

Single Responsibility Principle

Open/Closed Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

Continious integration

Tools

Test’em!

var assert = require("assert")

describe('Array', function() {
  describe('#indexOf()', function () {
    it('should return -1 when the value is not present', function () {
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(0));
    });
  });
});

describe('hooks', function() {

  before(function() {
    // runs before all tests in this block
  });

  after(function() {
    // runs after all tests in this block
  });

  beforeEach(function() {
    // runs before each test in this block
  });

  afterEach(function() {
    // runs after each test in this block
  });

  // test cases
});
var assert = require('chai').assert
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

assert.typeOf(foo, 'string'); // without optional message
assert.typeOf(foo, 'string', 'foo is a string'); // with optional message
assert.equal(foo, 'bar', 'foo equal `bar`');
assert.lengthOf(foo, 3, 'foo`s value has a length of 3');
assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');


var expect = require('chai').expect
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

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


var should = require('chai').should() //actually call the function
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
beverages.should.have.property('tea').with.length(3);
var assert = require('chai').assert
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

assert.typeOf(foo, 'string'); // without optional message
assert.typeOf(foo, 'string', 'foo is a string'); // with optional message
assert.equal(foo, 'bar', 'foo equal `bar`');
assert.lengthOf(foo, 3, 'foo`s value has a length of 3');
assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');



var expect = require('chai').expect
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

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



var should = require('chai').should() //actually call the function
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
beverages.should.have.property('tea').with.length(3);
it("calls the original function", function () {
    var callback = sinon.spy();
    var proxy = once(callback);

    proxy();

    assert(callback.called);
});


it("returns the return value from the original function", function () {
    var callback = sinon.stub().returns(42);
    var proxy = once(callback);

    assert.equals(proxy(), 42);
});


it("returns the return value from the original function", function () {
    var myAPI = { method: function () {} };
    var mock = sinon.mock(myAPI);
    mock.expects("method").once().returns(42);

    var proxy = once(myAPI.method);

    assert.equals(proxy(), 42);
    mock.verify();
});

JSCoverage

Istanbul

Books

https://github.com/kucherenko/tdd-javascript-template

 

http://c9.io

 

Questions?

Thank you!