Home > Uncategorized > Testable Web UI VII: Postback

Testable Web UI VII: Postback

February 19th, 2009

Last time, we got a bit derailed by some issues that were getting in the way of effective tests. Now that that’s all wrapped up, let’s implement selecting equipment.

The primary requirement here is that we have to display the player’s equipment, and when they select an item by clicking on it, we post to the server to save the player’s selection.

First, I populate the inventory list with the result of another JSON service. This is nothing new, really, so I will skip the details. However, I will need the __renderInventory function for the next step:

__renderInventory : function(inventory) {
    var n = this.__getChild('inventory');
    while (n.firstChild) {
        n.removeChild(n.firstChild);
    }

    for (var index in inventory) {
        var i = inventory[index];
        var li = document.createElement('li');
        li.setAttribute('class', 'inventory-item item-' + index);
        li.innerHTML = i.name;
        li.onclick = function(self, index) {
            self.__equipItem(index);
        }.bind(self, index);
        n.appendChild(li);
    }
},

__equipItem : function(index) {
    // TODO!
},

To write this test, I expanded FakeNetwork a bit, to encompass the ability to make POST requests as well as GET:

function FakeNetwork() {
    this.requests = {};
}

FakeNetwork.prototype = {
    asyncRequestJson : function(url, onComplete) {
        this.requests[url] = [null, onComplete];
    },

    post : function(url, data, onComplete) {
        this.requests[url] = [data, onComplete];
    },

    isRequestPending : function(url) {
        return url in this.requests;
    },

    getRequestData : function(url) {
        Assert.isTrue(this.isRequestPending(url), 'FakeNetwork: "' + url + '" has no data, as it is not pending');
        return this.requests[url][0];
    },

    completeRequest : function(url, result) {
        Assert.isTrue(this.isRequestPending(url), 'FakeNetwork: "' + url + '" is not pending.');
        this.requests[url][1](result);
        delete this.requests[url];
    },
};

This allows me to write the test:

testClickInventoryPostsToEquipService : function() {
    var sw = new StatusWidget('avatar-1', this.net);
    YAHOO.util.UserAction.click(getNode('avatar-1', 'equipment'));

    this.net.completeRequest('getInventory.json', [
        {name:'Clown Shoes', kickboxing: 9001}
    ]);

    Assert.isFalse(this.net.isRequestPending('updateEquipment.php'));
    YAHOO.util.UserAction.click(getNode('avatar-1', 'inventory-item item-0'));

    Assert.isTrue(this.net.isRequestPending('updateEquipment.php'), 'Service not dispatched');
    Assert.areEqual('Clown Shoes', this.net.getRequestData('updateEquipment.php').name);
},

The implementation is so simple as to be boring:

__equipItem : function(index) {
    this.net.post('updateEquipment.php', {name:this.inventory[index].name});
},

The last gap that is missing is that our real Network object can’t POST yet, but that is not sufficiently exciting to post here. (also, I don’t know how to write a functional test without writing some kind of CGI service, so I am going to cop out :D)

At this point, we’ve covered just about all of the major sticking points: introspecting CSS and the DOM at runtime, test isolation, asynchronous logic, mocking the network, and, of course, tests that are both reliable and really bloody fast: I ended with a pretty modest test suite of 7 tests that take (on my machine) 381ms to run. I’m about to call the experiment a raging success.

Tomorrow, I will talk about… something. It will be a surprise to everyone!

Source code

Uncategorized ,

Comments are closed.