Testing JavaScript

by Kevin Isom

Me

Pushpay

Testing JavaScript

Why test?

Why test

Testing frameworks

QUnit

QUnit.test( "hello test", function( assert ) {
  assert.ok( 1 == "1", "Passed!" );
});

Jasmine

describe("A suite", function() {
  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
  });
});

Mocha

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));
    });
  });
});

Chai - Should

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

Chai - Expect

var expect = chai.expect;
expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(tea).to.have.property('flavors')
  .with.length(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);

Power-Assert

var assert = require('power-assert');
describe('Array', function(){
  describe('#indexOf()', function(){
    it('should return index when the value is present', function(){
      assert([1,2,3].indexOf(0) === 2);
    });
    it('should return -1 when the value is not present', function(){
      assert.ok([1,2,3].indexOf(2) === -1, 'THIS IS AN ASSERTION MESSAGE');
    });
  });
});

Buster.js

Test Case

buster.testCase("My thing", {
    "has the foo and bar": function () {
        assert.equals("foo", "bar");
    },
    
    "states the obvious": function () {
        assert(true);
    }
});

Buster.js

Describe

buster.spec.expose();// Expose describe and it functions globally
describe("My thing", function () {
    it("has the foo and bar", function () {
        expect("foo").toEqual("bar");
    });
    it("states the obvious", function () {
        expect(true).toBe(true);;
    });
});

YUI Test

Test Case

var testCase = new Y.Test.Case({
  name: "TestCase Name",
  //traditional test names
  testSomething : function () {
      Y.assert(true === true, "Message")
  },
  testSomethingElse : function () {
      Y.Assert.isFunction(function(){}); 
  }
});

YUI Test

Test Case with text names

var testCase = new Y.Test.Case({
  name: "TestCase Name",
  //traditional test names
  "Something should happen here" : function () {
      Y.assert(true === true, "Message")
  },
  "Check something else" : function () {
      Y.Assert.isFunction(function(){}); 
  }
});

Legacy Code

The main thing that distinguishes legacy code from non-legacy code is tests, or rather a lack of tests.
Michael Feathers - Working Effictively With Legacy Code

Legacy Code

But JavaScript is hard to test

Right?

The way we write it makes all the difference

Things to avoid

Nested callbacks

var fs = require('fs');
fs.mkdir('./hello',0777,function(err){
  if (err) throw err;
  
  fs.writeFile('./hello/world.txt', 'Hello!', function(err){
    if (err) throw err;
    console.log('File created with contents: ');
    
    fs.readFile('./hello/world.txt', 'UTF-8', function(err, data){
      if (err) throw err;
      console.log(data);
    });
  });
});
var fs = require('fs');
fs.mkdir('./hello',0777,makeDirectory);
function makeDirectory(err){
  if (err) throw err;
  fs.writeFile('./hello/world.txt', 'Hello!', writeFile);
}
function writeFile(err){
  if (err) throw err;
  console.log('File created with contents: ');
  fs.readFile('./hello/world.txt', 'UTF-8', readFile);
}
function readFile(err, data){
  if (err) throw err;
  console.log(data);
}

Heavily chained jquery calls

Test Runners

Refactoring Sample 1

Refactoring Sample 2

Doing Something with it.

Break the build.

Next steps

Thanks