Friday, May 28, 2010

Testing a Binary Fab.js App (Almost)

‹prev | My Chain | next›

I think I have a handle on testing simple upstream fab.js apps. Tonight I will see if that ability transfers to testing binary (aka middleware) apps.

I have a middleware app that translates a new player response into a comet response:
  ( /^\/comet_view/ )
( init_comet )
( player_from_querystring )
The actual app looks like a normal, albeit large fab.js binary app:
function init_comet (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body) {
var downstream = out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })

// Send a bunch of output to initialize the comet session in some browsers
var downstream = out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })

({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
// ... many more of these lines to push pass Chrome's buffer

// Tell all other players about the new one
for (var id in players) {
downstream = downstream({body: comet_walk_player(JSON.stringify(players[id].status))});
}

// Add the new player to the player list
var new_id = obj.body.id;
puts("adding: " + new_id);
players[new_id] = {status: obj.body, listener: out};

// Establish an inactivity watch for the user
idle_watch(new_id);

// tell the user the game is aware of it
broadcast(comet_walk_player(JSON.stringify(obj.body)));
}
return listener;
});
};
}
Actually, wow. There is a lot going on there. I obviously didn't TDD that app. Let's see if I can drive a series of smaller apps. I break the init_comet app out of my main game.js application and into lib/init_comet. Since this is a binary app, I start with a skeleton binary app along with the initial comet reply:
var puts = require( "sys" ).puts;

function init_comet (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body) {
var downstream = out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })

// Send a bunch of output to initialize the comet session in some browsers
var downstream = out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })

({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
// ... many more of these lines to push pass Chrome's buffer
}
});
}
}
Next, I write my test. For now, I want to see if I can test that the middleware returns an HTML document back downstream:
exports.tests = ( function() {
var upstream = function() { this({body:{id:42}}); },
downstream = init_comet (upstream);

return [
function
bodyRespondsWithStartHtml() {
var out = this;
downstream.call(function(obj) {
out(obj.body == "<html><body>\n");
});
}
];
})();
I create a dummy upstream app. For now, it only needs to reply with a body. I then get the result of calling my init_comet app with that upstream app. If I have done this right, init_comet will call the supplied callback. I then send the result of my test back to the test harness with out. Unfortunately, when I run this, I get:
cstrom@whitefall:~/repos/my_fab_game$ node test.js
Running 1 tests...
bodyRespondsWithStartHtml: true

TypeError: undefined is not a function
at CALL_NON_FUNCTION (native)
at /home/cstrom/repos/my_fab_game/lib/init_comet.js:50:21
at listener (/home/cstrom/repos/my_fab_game/lib/init_comet.js:9:26)
at Function.<anonymous> (/home/cstrom/repos/my_fab_game/lib/init_comet.js:42:31)
at Function.<anonymous> (/home/cstrom/repos/my_fab_game/lib/init_comet.js:7:16)
at Function.bodyRespondsWithStartHtml (/home/cstrom/repos/my_fab_game/lib/init_comet.js:49:13)
at /home/cstrom/repos/my_fab_game/test.js:14:10
at Array.forEach (native)
at test (/home/cstrom/repos/my_fab_game/test.js:13:15)
at Object.<anonymous> (/home/cstrom/repos/my_fab_game/test.js:36:1)
Ah. Not too bad. The test passes, but that backtrace... Ew.

The init_comet app calls the downstream app with "<html><body>\n" and then calls the result of that with some of that comet buffer breaker data. I should simply need to return a function to be called:
exports.tests = ( function() {
var upstream = function() { this({body:{id:42}}); },
downstream = init_comet (upstream);

return [
function
bodyRespondsWithStartHtml() {
var out = this;
downstream.call(function(obj) {
out(obj.body == "<html><body>\n");
return function() {};
});
}
];
})();
Again, however, I get the same error:
cstrom@whitefall:~/repos/my_fab_game$ node test.js
Running 1 tests...
bodyRespondsWithStartHtml: true
Done. 1 passed, 0 failed.
TypeError: undefined is not a function
at CALL_NON_FUNCTION (native)
at listener (/home/cstrom/repos/my_fab_game/lib/init_comet.js:12:125)
at Function.<anonymous> (/home/cstrom/repos/my_fab_game/lib/init_comet.js:30:31)
at Function.<anonymous> (/home/cstrom/repos/my_fab_game/lib/init_comet.js:7:16)
at Function.bodyRespondsWithStartHtml (/home/cstrom/repos/my_fab_game/lib/init_comet.js:39:13)
at /home/cstrom/repos/my_fab_game/test.js:14:10
at Array.forEach (native)
at test (/home/cstrom/repos/my_fab_game/test.js:13:15)
at Object.<anonymous> (/home/cstrom/repos/my_fab_game/test.js:36:1)
at Module._compile (node.js:639:23)
I finally get this passing by returning the same listener:
    function
bodyRespondsWithStartHtml() {
var out = this,
already_tested = false;

downstream.call(function listener(obj) {
if (!already_tested) out(obj.body === "<html><body>\n");
already_tested = true;
return listener;
});
}
I must confess that I do not understand why returning the listener as opposed to an anonymous function worked. Something to investigate tomorrow.

Day #117

No comments:

Post a Comment