Wednesday, May 26, 2010

Testing with Fabjs (and a little commonjs thrown in)

‹prev | My Chain | next›

First up tonight, a bit of messing about with commonjs. It is a part of node.js, which means that it is a part of fab.js. That does not mean that I know much about.

Take export and require, for instance. I think I get what they do—allow library functions to be imported from a separate file into my current code. Seems straight forward, but I have not had need for it myself. Now that I am testing my fab apps, I think I will need it. Last time around, I broke the player_from_querystring method out into its own fab app in lib/player_from_querystring.js. In there, I had two exports statements:
function player_from_querystring() {
// the actual fab app here
}

exports.tests = ( function() {
// returns a list of functions that can be tested
})();

exports.app = player_from_querystring;
I am exporting two things: tests and app. The former goes into my test harness. The latter goes into my my (fab) game:
var puts = require( "sys" ).puts,
player_from_querystring = require('./lib/player_from_querystring').app;

// Other fab code

( /^\/comet_view/ )
( init_comet )
( player_from_querystring )
I could have exported the player_from_querystring directly:
exports.player_from_querystring = player_from_querystring;
And then imported thusly:
    player_from_querystring = require('./lib/player_from_querystring').player_from_querystring;
I prefer the fab.js convention of exporting app. I want to import a (fab) app, so it feels right.

With that out of the way, I am onto trying to test my player_with_querystring method. I got a rudimentary test (that this is a unary app) in place on Monday. Now, let's see if I can verify that, given a query string with player information, my unary app produces a player object.

This may not be a good first choice for experimentation. It is rather complicated in that it reads from the downstream (closer to the client) app to parse the query string:
function player_from_querystring() {
var out = this;
return function(head) {
if (head && head.url && head.url.search) {
var search = head.url.search.substring(1);
var q = require('querystring').parse(search);
var app = out({ body: {id: q.player, x: q.x || 0, y: q.y || 0} });
if ( app ) app();
}
else {
out();
}
};
}
So how to simulate that in the test harness? Well, I am definitely going to need a fake "head" (the request headers):
exports.tests = ( function() {
var head = { url: { search : "?player=foo&x=1&y=2" } };
return [

function
bodyRespondsWithCorrectPayload() {
// Test goes here
}
];
})();
But how to get that in there? If I execute player_from_querystring, it is going to return that downstream listener for the header. So I need to call player_from_querystring with something that can receive the response. In fab, it is the downstream app that receives the response. I create a fake downstream app to capture the player returned:
      var player;
function downstream (obj) {
if (obj) player = obj.body;
}
I am cheating a bit by manipulating player in that downstream app. There is almost certainly a cleaner way to do this, but... I am just trying to get this to work. With that, I want to call player_from_querystring with that downstream function:
      var listener = player_from_querystring.call(downstream);
That listener is what player_from_querystring returns to get the request headers (and the request body if there is one). This is where I need to pass in my head with the query string:
listener(head);
Last, but not least, I need to call the test harness with a boolean value indicating test pass / failure. I try comparing Javascript objects but fail. Instead I try comparing the id attribute, which should be "foo":
      out(player.id == "foo");
Altogether my test now reads:
exports.tests = ( function() {
var head = { url: { search : "?player=foo&x=1&y=2" } };

return [
function
statusReturnsUnaryApp() {
this( player_from_querystring().length === 1 );
},

function
bodyRespondsWithCorrectPayload() {
var out = this;
var player;
function downstream (obj) {
if (obj) player = obj.body;
}
var listener = player_from_querystring.call(downstream);
listener(head);
out(player.id == "foo");
}

];
})();
And, when I run the tests:
cstrom@whitefall:~/repos/my_fab_game$ node test.js 
Running 2 tests...
statusReturnsUnaryApp: true
bodyRespondsWithCorrectPayload: true
Done. 2 passed, 0 failed.
Yay!

It is going to be a while before I am TDDing fab.js applications. But this is definite progress.

Day #115

No comments:

Post a Comment