Function-wrapping and BDD-style specs in javascript

I’ve been spending too much time with javascript lately. It probably isn’t healthy for me. I’ll get back to my own Clojure project and paying Ruby work soon enough, but right now I’m working on an interface-intensive project, so I’m up to my neck to explicit returns and key:value notation.

I was looking into improving my code (less coupling, more encapsulation, greater readability for me when I inevitably come back to extend it), and that led to some playing around with call, apply, and a prototype.js library method, Function#wrap, that puts this sort of casting and scope-alteration to use to enable metaprogramming. I sensed I was skirting the edge of a real revelation about how functional programming meets OOP in languages like javascript. But that’s for another post. Right now, I’d like to bite off more than I can chew on a different topic: how straightforward it appears to be to implement some RSpec-like Behavior-Driven Development specification-testing magic on javascript objects using Function#wrap.

It’s been famously said that almost everything in javascript is a hash. Remarkably true. But it’s also noteworthy that functions are objects (and some objects are functions). If you define a function “foo”, your new function is an object with its own methods: member functions. For example, foo.apply and foo.call. The Prototype.js library contributes one of these member functions, wrap, which I present here (version 1.6):


wrap: function(wrapper) {
var __method = this;
return function() {
return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
}
}

What we have here, then, is a function wrap, available as a method on any function, that takes another function (the “wrapper”) as a parameter and returns another function. It doesn’t get much more functional than this. One more detail: the returned function, when called, is going to execute the wrapper function, and it’s going to pass the original (“wrapped”) function as the first argument to the wrapper.

Wow. If the source code for wrap seems a tad dense to you, you aren’t alone. It’s easier to explain with examples. I recommend an excellent article at the blog The If Works for more explanation and examples.

Metaprogramming up a spec

My interest in Function#wrap is how it can be used to set up expectations about how functions will be called. The programming school around behavior-driven development (BDD) advocates a form of test-first code-writing in which the tests are considered specs, or expectations about how some code will behave. Domain-specific languages for BDD usually feature methods and constructs like “should” and “expect”. In RSpec, for Ruby, for example, you can express these expectations with code like:

@range.letter_grade_for(89.5).should ==("A")

This spec/test establishes that the range object should translate the number grade 89.5 into a letter grade of “A”. (Our grading scale is relaxed here at Autodidact.) When the spec code runs, it will try out calling the letter_grade_for method with the value 89.5, and if it doesn’t get “A” back then it will let you know.

Where do these expectations live? And how are they kept track of? There’s some heavy metaprogramming at work, and I don’t pretend to understand it well enough to explain it. What I can do is sketch out a way to implement this kind of metaprogramming in javascript.

Let’s take a simpler expectation. Say we’re writing a spec for some code and we want to establish the expectation that a function will get called with a certain value. Let’s make this function really simple:

double_function = function(x) {
return x * 2;
}

Simple. Our function takes a number and returns the number times two.

Imagine we want to spec out that double_function will receive 2. Once simple way to do this is to replace double_function with a wrapped function that will intercept the argument passed to it and be able to ascertain whether it is 2 or not. To avoid breaking other spec code that may depend on double_function returning a (correct) value, the wrapped function should also call double_function with its argument.

I’d like to be able to express it like this:

double_function = double_function.should_receive(2);

Here’s a one-off way to accomplish this:

double_function.should_receive = function(expected) {
return double_function.wrap(function(orig_function, arg) {
alert(expected + " ?= " + arg + " " + (expected == arg));
return orig_function(arg);
});
}

Now this code pops up an alert window with “2 ?= 2 true”:

double_function(2);

How can we have should_receive for any function we want to spec? In javascript, monkey patching is both easy and common:

Function.prototype.should_receive = function(expected) {
return this.wrap(function(orig_function, arg) {
alert(expected + " ?= " + arg + " " + (expected == arg));
return orig_function(arg);
});
}

Now any function you define has a should_receive method.

Another common task in writing BDD specs is stubbing an auxiliary object’s behavior, or a main object’s secondary behavior. We may want to express our expectations in a case in which it’s a given that double_function will be called and return 5. There’s no need for the spec-writer to call double_function with the correct argument to return 5. This is a very simple example. You can imagine much more complicated cases in which you really wouldn’t want to worry about defining the argument.

Function.prototype.stub_return = function(stubbed_return) {
return this.wrap(function(orig_function, arg) {
orig_function(arg);
return stubbed_return;
});
}

double_function = double_function.stub_return(5);

Now calling double_function with any argument will return 5. Notice that I include a call to the orig_function, the wrapped function. This is a choice I made so that I could chain my BDD methods:

double_function = double_function.should_receive(2).stub_return(5);

To unwrap the multiply-wrapped functions, we have to call the wrapped function, or else the should_receive(2) expectation won’t be tested. If I excluded the call in stub_return function, the double_function function would never be executed. This might be something we want. If the function is expensive, or has side effects we don’t care about (or want to prevent), then just returning the desired value would be an excellent outcome. Maybe I should introduce a flag member, like “has_expectations” that is set on the function that should_receive returns. Then stub_return could call the function it wraps only if the flag is present.

This is very primitive, granted. There’s no way to collect the results of the specs and report them later. I imagine a global object could register expectations and be notified whenever one was met or not. I’m also a little unhappy with having to be explicit about redefining a function with the return value of wrap. But I only wanted to explore how useful something like Function#wrap can be to accomplish sophisticated metaprogramming tasks. And I should really take a break from javascript before it’s too late to come back.