Unit testing the frontend – Today's Topics Include...



Unit testing the frontend – Today's Topics Include...

0 0


codecamp_2012_presentation

Codecamp 2012 Presentation

On Github rob-odil / codecamp_2012_presentation

Unit testing the frontend

Navigating the minefield of client side testing

Presenters: Rob Odil & Brendan Hill

Today's Topics Include...

  • Ember :: MVC framework for the browser
  • Qunit :: Unit testing framework for js
  • Sinon :: Mocking library for use in unit testing
  • PhantomJS :: Headless Webkit browser

Huh?!?!

What's This Ember Thing?

  • Model/View/Controller framework
  • Databinding to the dom
  • Clientside templating (Handlebars)

Object Binding Example

MyApp.president = Ember.Object.create({
    name: "Barack Obama"
});

MyApp.country = Ember.Object.create({
  // Ending a property with 'Binding' tells Ember to
  // create a binding to the presidentName property.
  presidentNameBinding: 'MyApp.president.name'
});

// Later, after Ember has resolved bindings...
MyApp.country.get('presidentName');

// "Barack Obama"

DOM Binding Example

In the handlebars template:
<p>Property {{App.model.property}}</p>
With javascript code:
App.model = Ember.Object.create({
  property: 'is set'
});
Browser output: Property is set

What's Qunit Do For Me?

  • Javascript unit testing framework
  • Basic module system
  • Simple set of assertions
  • Reports passed/failed tests

Testing an API Example

var easyMath = {
    timesTwo: function (input) {
        return input * 2;
    }
};

test('Make sure math works', function () {
    equal(6, easyMath.timesTwo(3), '3 * 2 = 6');
});

Testing the DOM Example

var pageBuilder = {
    insertFooter: function () {
        $('body').append('<div id="footer"></div>');
    }
};

test('Make sure footer can append', function () {
    pageBuilder.insertFooter();

    equal($('#footer').length, 1, 'Footer did append');
});

Async Testing Example

test('Testing async operations', function () {
    stop();
    setTimeout(function () {
        ok('Something' !== 'else', 'always pass');
        start();
    }, 1000);

    ok('thing' === 'thing', 'always pass');
});

Ember Testing Gotcha

  • Views and bindings aren't real time
  • Ember resolves bindings in runloops

Ember Test Example

App.Model = Ember.Object.extend({
    fname: '',
    lname: '',
    fullName: function () {
        return this.get('fname') + ' ' + this.get('lname');
    }.property('fname', 'lname')
});

test('Test that full name works', function () {
    Ember.run.begin();
    var mdl = App.Model.create({});
    mdl.set('fname', 'John').set('lname', 'Doe');
    Ember.run.end();

    equal(mdl.get('fullName'), 'John Doe', 'Magic happened.');
});

Sinon? Never heard of it!

  • Test spies
  • Stubs and mocks
  • Isolating AJAX tests

Sinon Test Spy Example

var testCallbacks = function (callback) {
    // Do the work. maybe update the dom
    $('body').append('something added');
    return callback();
};

test('Sample sinon spy in action', function () {
    var theSpy = sinon.spy();
    testCallbacks(theSpy);

    ok(theSpy.calledOnce, 'Our spy was called.');
});

Sinon Stub Example

var testStubs = function (callback) {
    // Do the work. maybe update the dom
    $('body').append('something added');
    return callback();
};

test('Sinon stub used to force return value', function () {
    var theStub = sinon.stub().returns(42);

    equal(testStubs(theStub), 42, 'Returned value is correct');
});

Sinon Mock Example

var apiToBe = {
    futureMethod: function () {
        // doesn't do anything yet
    }
};

test('sinon api fakes implementation of api', function () {
    var mock = sinon.mock(apiToBe);
    mock.expects('futureMethod').once().returns(42);

    equal(apiToBe.futureMethod(), 42, 'The returned value is correct');
    ok(mock.verify(), 'Verify that the method was only called once');
});

Sinon AJAX Test Example

function getServerData() {
    jQuery.ajax({
        url: "/data/",
        success: function (data) {$('body').append(data.key);}
    });
}

test('Test a fake ajax request', function () {
    var fakeServer = sinon.fakeServer.create();
    getServerData();
    fakeServer.requests[0].respond(200,
        {'Content-Type': 'application/json'},
        JSON.stringify({key: 'value'})
    );
    
    equal('/data/', fakeServer.requests[0].url, 'Used correct URL');
    equal($('body').text(), 'value', 'Dom was updated');
    fakeServer.restore();
}

PhantomJS? Sounds scary!

  • Webkit at the command line
  • Run test suit outside the browser
  • Integration with other tools

Phantom Use Cases

  • Using a file watcher for change feedback
    • Grunt
    • Yeoman
  • Integrating with continuous integration system
    • Jenkins / Hudson
    • Travis CI
    • Team City

A Sample Application

Demo Time

Links & Contact

Comments, Questions, Suggestions?

Fire away!!!