I encountered some difficulties yesterday trying to use vows.js to test a binary fab.js app. Hopefully I can do a bit better today.
At the end of yesterday's work, I came to the conclusion that my difficulties stemmed from a mistaken belief that binary and unary (fab) apps were inherently the same—at least from a testing perspective. That is not as silly as it sounds. A binary (fab) app + a unary app == a unary app. My mistake was in not recognizing that the preconditions were different.
The unary app on which I cut my vows.js teeth responded to input from downstream apps (e.g. the browser). The binary (middleware) app that I was trying to test yesterday responded to input from upstream apps. I eventually corrected my mistake and ended up with a decent vows.js test for my comet initialization (fab) app:
'init_comet').I rather like that. When the upstream app replies with a player object, this binary app should set a cookie in response.
addBatch({
'with a player': {
topic: api.fab.when_upstream_replies_with({body: {id:1, uniq_id:42}}),
'sets a session cookie': function(obj) {
assert.equal(obj.headers["Set-Cookie"], "MYFABID=42");
}
}
})
That is all well and good, but my challenge today surrounds how fab.js supports sending multiple chunks of replies. It does so via downstream listeners that return themselves to listen for more responses. An empty response signals to downstream (or the browser) that the connection can be closed. Since I am testing a comet connection, I do not want to close the connection.
The structure of the
init_comet
(fab) app being tested looks like:function init_comet (app) {The chain of function
return function () {
var out = this;
return app.call( function listener(obj) {
if (obj && obj.body) {
out({ headers: { "Content-type": "text/html",
"Set-Cookie": "MYFABID=" + obj.body.uniq_id } })
({body: "<html><body>\n" })
({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
// ... More of these dummy scripts to force chrome to recognize the comet stream
(obj);
}
return listener;
});
};
}
out
returning a function that gets called and returns another function that in turn gets called, was fairly easy to support in vows.js—I did it the same way that fab.js does it:when_upstream_replies_with: function(obj) {The challenge that I have left for myself today is how do I test that the second response from my middleware is the opening of an HTML document? As written, my API call will only set the topic of the test to the first response—the
return function () {
var topic = this;
var upstream = function() { this(obj); },
unary = init_comet(upstream);
unary.call(
function(obj) {
topic.callback(null, obj);
return function listener() {return listener;};
}
);
};
}
topic.callback
. All other chunks are lost to the listener
function that does nothing but return itself.In the end, I decide that the topic of my from-downstream-tests needs to collate all of the chunks:
var api = {That's a bit of a hack because one of the things that I will be testing is that my chunks will include the upstream (player) object and my API call is deciding that the topic is ready when the current chunk is the same as the supplied fake player.
fab: {
when_upstream_replies_with: function(upstream_obj) {
return function () {
var topic = this;
var upstream = function() { this(upstream_obj); },
unary = init_comet(upstream);
var chunks = [];
unary.call(
function listener(obj) {
chunks.push(obj);
if (obj == upstream_obj || typeof(obj) == "undefined") {
topic.callback(null, chunks);
}
return listener;
}
);
};
}
}
};
I am not happy with that, but I have no other good way of solving this. I cannot use the absence of a call to my downstream listener as the signal that the topic ready. Well, I could try setting a timeout, but that seems even more hackish than what I already have. I will give this a go and change as needed.
Happily, that makes my actual test rather nice:
'sets a session cookie': function(chunks) {True, I do have to have a bit more knowledge of the order of my chunked replies, but the order is something set in my target (fab) app, so that knowledge seems OK to me. I can even use the array of chunks to test that I can poke Chrome into recognizing data in the comet stream:
assert.equal(chunks[0].headers["Set-Cookie"], "MYFABID=42");
},
'sends the opening HTML doc': function(chunks) {
assert.match(chunks[1].body, /<html/i);
},
'sends the player': function(chunks) {
assert.deepEqual(chunks[chunks.length-1], {body: {id:1, uniq_id:42}});
}
'send 1000+ bytes to get Chrome\'s attention': function(chunks) {That could be cleaned up a bit if I were using a Javascript library like underscore.js, but I will leave that for another day. A few more tests and I have my
var byte_count = 0;
for (var i=0; i < chunks.length; i++) {
var chunk = chunks[i];
if (chunk && chunk.body && typeof(chunk.body) == "string") {
byte_count = byte_count + chunk.body.length;
}
}
assert.isTrue(byte_count > 1000);
},
init_comet
(fab) app completely covered:cstrom@whitefall:~/repos/my_fab_game$ vows --specI may look to try adding underscore.js to my testing toolkit tomorrow. That or I will give testing another binary (fab) app a try.
♢ init_comet
with a player
✓ sets a session cookie
✓ sends the opening HTML doc
✓ send 1000+ bytes to get Chrome's attention
✓ sends the player
without a player
✓ terminates the downstream connection
with an invalid player object
✓ terminates the downstream connection
Day #157
No comments:
Post a Comment