Thursday, September 13, 2012

knockout.composite: Integration Testing: Playing Nicely Together

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

This is part 6 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 parts 4 and 5.

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

 

The JavaScript / HTML / CSS technology stack has some serious advantages over traditional desktop technology stacks when it comes to testing, particularly the fact we can run our tests in any browser, from anywhere, in any environment (dev / test / prod / etc) and with no extra software. Additionally, the highly integrated nature of JavaScript and HTML make it trivial to perform assertions against rendered markup.

So what exactly do I mean by integration testing?

knockout.composite allows us to render a single pane into any element. This may consist of a number of child panes, so we can test the interactions between these panes. We can also make assertions about the rendered UI, testing the integration between models and views. Let’s have a look at some examples.

Some infrastructure…

Before we start rendering panes and making assertions against results, we need a mechanism for rendering the pane and waiting for the render operation to complete before performing any assertions. Unfortunately, due to some calls to setTimeout, we lose the ability to run our tests synchronously, so we need to make use of qunit’s support for asynchronous tests.

We’ll use the addPane utility function to render the pane and then wait for the childrenRendered message to be published before we start making assertions.

function render(options, tests) {
    var pane = ko.composite.utils.addPane($('#qunit-fixture')[0], options);
    pubsub.subscribe('childrenRendered', function () {
        tests();
        start();
    });
    return pane;
};

You can see the tests in action here.

A simple test

We’re going to perform some integration tests against the webmail sample we built in part 4. We can use the same configuration file and index.html as we did for unit testing. Let’s see how it works.

asyncTest("Folders pane renders the correct number of buttons", function() {
    render('folders', function() {
        equal($('.folders li').length, 4);
    });
});

Looks pretty simple. Render the pane. Make sure there are four list items.

Interacting with the UI

Since we’ve got our pane rendered and sitting there in our browser, why not poke it a bit?

asyncTest("Clicking a folder name sets the selected class", function () {
    render('folders', function () {
        ok($('.folders li:contains("Sent")')
            .click()
            .hasClass('selected'));
    });
});
Accessing the model

The pane object exposes a model property that contains the underlying model. Use this if you want to make assertions against the model after rendering the pane.

asyncTest("Clicking a folder name sets the selectedFolder property", function () {
    var pane = render('folders', function () {
        $('.folders li:contains("Sent")').click();
        equal(pane.model.selectedFolder(), 'Sent');
    });
});

Accessing the model of child panes can be done by using the knockout function ko.dataFor.

Doing More

That’s all I’m going to show for now, but this is just a small taste of what can be tested with this approach. See the tests in action here.

Next Up!

Next post, we’re going to take a quick look at some complete, automated, end to end functional testing for your application.

0 Comments:

Post a Comment

<< Home