Wednesday, September 12, 2012

knockout.composite: Unit Testing: Model Citizens

*UPDATE* Tribe is here! Check out http://tribejs.com/ for guides and API reference.

This is part 5 of a multi-part post. It’s assumed that you have a basic knowledge of the data binding and observability features of knockout.js and have read part 4.

Part 1: Introducing knockout.composite
Part 2: The Basics
Part 3: Getting Around
Part 4: Putting it All Together
Part 5: Unit Testing: Model Citizens 
Part 6: Integration Testing: Playing Nicely Together
Part 7: Functional Testing: Automated UI Tests, Anywhere, Any Time
Part 8: Pack Your App and Make it Fly
Part 9: Parting Thoughts and Future Directions

 

knockout.composite gives you the ability to easily unit test your individual models, test the integration between panes and perform full functional testing of your application, all from within a browser.

This post covers the first of these – unit testing your models. We’ll be using qunit (the JavaScript unit testing framework used by jQuery), the excellent sinon.js mocking library and mockjax for stubbing out ajax calls.

Unit Testing Your Models

For this example, we’re going to write some tests against models from the webmail sample we wrote in part 4. You can see the tests in action and the complete source for the tests on github.

The index.html file contains the necessary script and CSS references to fire up qunit and knockout.composite.

As we’re testing panes from the webmail sample, we need to let knockout.composite know where to load panes from. We’ll also tell it to load things synchronously. This simplifies our tests considerably. We’ve put this in a separate configuration file in the Resources folder.

ko.composite.options.basePath = '../6. Webmail/4. The Final Pieces/';
ko.composite.options.synchronous = true;
Creating models

So how do we load up our model and create an instance of it? The ko.composite.resources namespace contains a function called loadModel that loads the model for us, then we can just create an instance of it. Remember, model constructors take three arguments – pubsub, data, pane.

ko.composite.resources.loadModel('folders');
var model = new ko.composite.models['folders'].constructor(pubsub, data, pane);

We’ve separated that out into a helper function called createModel to simplify our code a bit.

A simple test

This is a simple test against the model for the folders pane. It asserts that the value of an observable changes when the selectFolder function is called.

test("selecting a folder sets selectedFolder observable", function () {
    var pubsub = { publish: function () { } };
    var model = createModel('folders', pubsub);
    model.selectFolder('Sent');
    equal(model.selectedFolder(), 'Sent');
});

Pretty simple stuff. Create the model, call the selectFolder function and assert that the value of the selectedFolder observable has been set. The pubsub variable is created because we know that the selectFolder function calls the pubsub.publish function. If this function doesn’t exist, an exception will occur.

Making sure messages are published

A sinon spy is perfect for this. This time, instead of an empty function, we’ll attach a spy to the publish function. The spy gives us access to information about calls made and arguments passed.

test("selecting a folder publishes folderSelected method", function () {
    var pubsub = { publish: sinon.spy() };
    var model = createModel('folders', pubsub);
    model.selectFolder('Sent');

    ok(pubsub.publish.calledOnce);
    equal(pubsub.publish.firstCall.args[0], 'folderSelected');
    equal(pubsub.publish.firstCall.args[1], 'Sent');
});

Our spy has replaced the publish method, so we can assert that the selectFolder function also publishes the folderSelected message with appropriate arguments.

Testing message subscribers

This time, we’re going to test the layout model and it’s message subscription functions. To invoke these, we can create a real instance of pubsub, pass it to the model constructor and call the publish method. We’ve also mocked out the pane.navigate function with a spy.

test("model navigates when folderSelected message is published", function () {
    var pubsub = new PubSub({ forceSync: true });
    var pane = { navigate: sinon.spy() };
    var model = createModel('layout', pubsub, null, pane);
        
    pubsub.publish('folderSelected', 'Sent');
    ok(pane.navigate.calledOnce);
    equal(pane.navigate.firstCall.args[0], 'mails');
    deepEqual(pane.navigate.firstCall.args[1], { folder: 'Sent' });
});
Mocking AJAX calls

Last little trick – stubbing out your AJAX requests with mockjax. This is not actually part of knockout.composite, but is useful enough to warrant a mention here. This test exercises the initialise function of the mails pane.

test("initialise loads specified folder and sets data observable", function () {
    $.mockjax({
        url: '../data/folder/Sent',
        responseText: JSON.stringify({ test: 'test' }),
        responseTime: 0
    });

    var model = createModel('mails', null, { folder: 'Sent' });
    model.initialise();
    deepEqual(model.data(), { test: 'test' });
});

Again, pretty simple stuff. Set up the AJAX call, create the model, call stuff, test stuff.

Check the running tests out here.

Until Next Time…

Integration testing up next. See you then!

0 Comments:

Post a Comment

<< Home