Javascript Testing

Fast, simple tests that fit in your workflow

Key Takeaway

Javascript testing doesn't have to be complex

  • You don't need browsers or headless runners like phantomjs to get good coverage
  • CI Integration is easy with mocha

Terminology

Terminology

Unit Test

A test which tests the logic in a particular unit of code

Terminology

Javascript Unit Test

A test which tests the logic in a particular function

Don't test jQuery, test that you're calling jQuery.

Don't test AJAX, test that you're making a request.

Terminology >

Javascript Unit Test

Spy
Wraps a function and watches arguments. Calls the original function.
Stub
Replaces a function and watches arguments. Does not call the original function*.
Mock
Replaces funcitons and properties on an object. Does not call the original*.
* Unless you tell it otherwise.

Terminology >

Javascript Unit Test

var className = 'active',
    el = {
      addClass: sinon.spy();
    };

myModule.$el = el;
myModule.activate();

expect(el.addClass).calledWith(className);

// just test that it's called.
// we don't need to test if jQuery set the class.
// we can assume jQuery actually works.

Terminology

Integration Test

A test which tests the result of systems interacting

Terminology

Javascript Integration Test

A test which tests the result of functions calling other functions

Terminology >

Javascript Integration Test

myModule.activate();
expect(myModule.$el.hasClass("active")).to.be.true;

Terminology

Functional Test

A test which tests for the state of an application after interacting with the user interface

Terminology

Javascript Functional Test

A test which tests for the state of the page after interacting with the DOM

Writing Tests

Writing Tests

Using Mocha

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

Writing Tests >

Using Mocha

Writing Tests >

Using Mocha

Chai for Assertions

var chai = require('chai'),
    expect = chai.expect;

var Emitter = require('../src/js/emitter.js');

describe('binding with `on`', function(){
  /* -- tests -- */

  it('should do nothing if no event name is given', function(){
    var e = new Emitter();
    e.on();
    expect(e.on).to.not.throw(Error);
    expect(e.queue).to.be.empty;
  });
});

Writing Tests >

Using Mocha

Sinon for Spies & Stubs


sinon = require('sinon');
sinonChai = require('sinon-chai';

describe('calling events with `emit`', function(){
  it('should call all the functions bound in the queue', function(){
    var e = new Emitter(),
        fn = sinon.spy();

    e.queue['test'] = [fn];

    e.emit('test', 1, 2);

    expect(fn).calledWith(1, 2);
  });
});

Writing Tests >

Using Mocha >

The Coffeescript Slide

describe 'my object', ->
  it 'should do a thing', ->
    sinon.spy(myObject', 'doSomething')
    myObject.doSomething(7)
    expect(myObject.doSomething).calledWith(7)

Writing Tests >

Using Mocha >

myFunction.js

var myFunction = function(){
  /* stuff */
};

module.exports = myFunction;

myOtherFunction.js

var myFunction = require('./myFunction.js');
myFunction("HI GUYS I'M DOING A THING");

Writing Tests >

Using Mocha >

Browserify

Writing for Compatibility

myFunction.js

!function(global){
  var myFunction = function(){
    /* stuff */
  };

  // export it using Browserify if we can
  if(module && module.exports){
    module.exports = myFunction;

  // otherwise just attach it to global, which is `window`
  }else{
    global.myFunction = myFunction;
  }
}(this);

Your Project

Let's Get this Rolling

Let's improve reliability

Let's improve documentation

Let's improve code reuse

Let's improve code complexity

Don't Wait Until Your Users Are Mad!

Mocha
Documentation and instructions at visionmedia.github.io/mocha
Chai
Documentation and instructions at chaijs.com
Sinon
Documentation and instructions at sinonjs.org