Sunday, July 4, 2010

Asynchronous Testing of Fab.js with Vows.js

‹prev | My Chain | next›

I continue working through vows.js testing for fab.js today. I am still working with my simple player_from_querystring unary (fab) app. At this point, I have two different "topics" (vows terminology for things to be tested) and several "vows" (unit tests in vows-speak). My goal today is to move beyond what I know about testing and into what vows is try to get me to do.

I have two test batches so far, one for 'with a query string' and the other for 'POSTing data'. The things that I am testing, the topics, in both cases are very similar. The first:
    'with a query string': {
topic: function() {
var response;
function downstream (obj) { response = obj; }

var upstream_listener = player_from_querystring.call(downstream);

upstream_listener({
url: { search : "?player=foo&x=1&y=2" },
headers: { cookie: null }
});

return response.body;
},
And the second:
     'POSTing data': {
topic: function() {
var response;
function downstream (obj) { response = obj; }

var upstream_listener = player_from_querystring.call(downstream);

upstream_listener({body: "foo"});

return [response];
},
The first three lines of each are identical. The return statement for both would be the same if vows.js allowed undefined topics. The real difference between the two is what gets set by the downstream (e.g. the browser). When I am testing the query string, I send only HTTP header stuff. When I am testing what happens when POSTing the data, I only send a body.

If I understood this better, then I would DRY up the code. Since I am learning, I think it makes more sense to try to get one of them right, apply what I know to the other and then DRY things up. This may blow up in my face, but that is what git checkout is for.

I will start with the second of the two since there is only one vow/test written against it and because it was obviously awkward when I wrote it. What made it feel awkward was, as I mentioned earlier, that I was testing for an undefined response, but vows does not allow undefined topics. Actually, that is not entirely accurate, it allows for undefined responses, but they must be accompanied by the use of this.callback.

The this.callback function seems well-named—vows.js uses it to keep track of asynchronous callback functions. It ensures that they do get called and then send the arguments that it receives onto the vows/tests. Let's see if I can replace the downstream app with a this.callback in my POSTing test like so:
   addBatch({
'POSTing data': {
topic: function() {
var upstream_listener = player_from_querystring.call(this.callback);
upstream_listener({body: "foo"});
},

'is null response': function (obj) {
assert.isUndefined (obj);
}
}
})
Ooh... I like that. I like it a lot.

The topic section only has the code I wanted. Gone are the identical first three lines of code between my two topics as well as the weird return. Only the topic code that is important is left. Awesome!

But... does it work?
cstrom@whitefall:~/repos/my_fab_game$ vows --spec

♢ just_playing

with a query string
✓ is player
✓ has unique ID
✓ has X coordinate
✓ has Y coordinate
POSTing data
✓ is null response


✓ OK » 5 honored (0.096s)
Nice. There may just be something to this vows.js stuff.

So let's see if I can apply this to my more involved happy path:
  addBatch({
'with a query string': {
topic: function() {
var upstream_listener = player_from_querystring.call(this.callback);
upstream_listener({
url: { search : "?player=foo&x=1&y=2" },
headers: { cookie: null }
});
},

'is player': function (obj) {
assert.equal (obj.body.id, "foo");
},
//...
}
})
Oooh... that's equally nice. All that is left is the barebones topic—only what matters. Sadly, however, that does work for me:
cstrom@whitefall:~/repos/my_fab_game$ vows --spec

♢ just_playing

with a query string
✗ is player
» An unexpected error was caught: [object Object]
✗ has unique ID
» An unexpected error was caught: [object Object]
✗ has X coordinate
» An unexpected error was caught: [object Object]
✗ has Y coordinate
» An unexpected error was caught: [object Object]
POSTing data
✓ is null response

✗ Errored » 1 honored ∙ 4 errored (0.087s)
Dang.

With the help of well-placed puts, I figure out that I am not even reaching my tests. A little more investigation tells me that I cannot pass just anything to this.callback, I need to pass both an error object and a real object. Bah! That means a bit of an impedance match between vows.js and fab.js.

Rather than sending this.callback directly to my (fab) app, I have to pass in a proxy:
  addBatch({
'with a query string': {
topic: function() {
var topic = this;
var upstream_listener = player_from_querystring.call(
function(obj) {
topic.callback(null, obj.body);
}

);
upstream_listener({
url: { search : "?player=foo&x=1&y=2" },
headers: { cookie: null }
});
},

'is player': function (player) {
assert.equal (player.id, "foo");
},
// ...
}
})
The nice thing about this is that I can pass the body attribute from my (fab) app to my tests. The bad thing is the added code. That is a good stopping point for tonight. I am a bit better off than I was last night. Hopefully tomorrow will be even better.


Day #154

No comments:

Post a Comment