Casual testing

Sometimes you use ready made frameworks because they’re robust and well tested and you just have to get stuff done. And that’s fine. Sometimes you write code because it’s new and you’re really doing something novel. That’s super cool too. And then sometimes you write code just so you have something that fits your weird way of thinking, so you know it inside and out, and so you’ve gained the experience of writing it. This last category seems like it should be called “casual programming”. That’s probably not a term that would benefit from a rigorous definition, but suffice it to say it’s not the kind of programming anyone usually has the luxury of doing at work where you need to get-stuff-done or provide-value-to-the-customer.

Jest is an open source JavaScript testing framework from Facebook. It’s a neat system with some interesting applications of functional programming ideas. But if you’re filled with the do-it-yourself spirit and not totally focused on raw productivity, then maybe the ideas are more interesting than the thing itself. So that’s what this post is about: having some fun stealing the two best ideas from Jest to build a tiny test framework.

The expect function

Conventional testing would organize a bunch of testing procedures where each procedure checks whether or not some code produces the right results. For example, a testing procedure for an add function might check if add(1, 1) returns 2. In Python you can write your tests with the unittest module, which provides a bunch of methods with names like assertTrue or assertEqual, so a test of the add function might be written self.assertEqual(add(1, 1), 2).

The expect function serves the same purpose as these assert methods but adds a monad-like flavor, which of course makes it extremely hip and extremely cool. Using the expect function, the same test would be written expect(add(1, 1)).equals(2). This also puts the arguments into a nice ordering where the verb (equals) appears between the grammatical subject (the result of add(1, 1)) and the grammatical object (2).

To implement expect, all we need is a class that holds the value passed to the expect function that also implements an equals method. I called this class Expectation, which seemed like the obvious choice.

export const expect = (value) => {
    return new Expectation(value);
}

class Expectation {
    constructor(value) {
        this.value = value;
    }

    equals(other) {
        if(this.value != other) {
            throw new Error(`${this.value} != ${other}`);
        }
    }
}

Adding more “verbs” is a matter of adding more methods to Expectation. If the burden of verbs is getting unwieldy, the methods could be refactored into sub-classes that specialize for the type passed to expect so that if the value in expect(value) is an instance of Array, then an ArrayExpectation might have a special method like contains and a NumericExpectation might have a method like isGreaterThan. If you’re into chaining, expectation methods could also return this from each method to support tests such as:

expect(add(1, 1))
    .isGreaterThan(1)
    .isLessThan(3)
    .equals(2)

A masque or a mock function

Jest uses the name ‘mock functions’ to describe how they test higher order functions—functions that take functions as arguments. In a test, you would pass a mock function to a callback, trigger the condition that executes the callback, and then check with the mock function to see if it has been executed as expected. For example, if we want to test that the Window object is dispatching a custom event, we could do this manually:

var gotCall = false;
window.addEventListener('custom', (event) => {
    gotCall = true;
});

window.dispatchEvent(new CustomEvent('custom'))
expect(gotCall).equals(true);

But wouldn’t it be nice if we could be more concise and pass a magic handler that handled setting up this gotCall variable for us? Something like this:

window.addEventListener('custom', magic);
window.dispatchEvent(new CustomEvent('custom'));
expect(magic).wasCalled();

So what would this magic thing have to look like in order to work in this code snippet? It would have to be callable as a function, and it would also have to somehow report back whether or not it had ever been called.

You could do this with a Proxy handler that implements the apply method (see here). Proxies are pretty verbose, and I’ve never liked the triplicate nature of the proxy system with the proxy, target, and handler being three distinct things.

Instead, this is a perfect opportunity to use a closure. I was inspired by the procedural implementations of a linked list in The Structure and Interpretation of Computer Programs (in the last fifteen minutes of this video). In idiomatic ES6, you can make a linked list out of closures and higher order functions without using any underlying arrays or objects for data storage:

const pair = (head, tail) => {
    return (pick) => {
        if(pick)
            return head;
        else
            return tail;
    }
}

const head = (list) => {
    return list(true);
}

const tail = (list) => {
    return list(false);
}

Thus head(pair('a', 'b')) == 'a' and tail(pair('a', 'b')) == 'b'. Chaining together pairs lets you create a list: var x = pair('a', pair('b', 'c')). Here head(x) == 'a' and tail(tail(x)) == 'c'.

Now we have a data structure with no visible underlying means of storing data: it’s all just functions. But really the variables head and tail are in fact stored in the function’s environment, which we call a closure. Closures are pretty much just objects that store the names of the variables in the current scope associated to their memory location. But unlike regular JavaScript objects they’re totally implicit and the closure itself can’t be called by name or passed as an argument.

My implementation of masque uses a trick similar to the pick parameter in the linked list example, but since we want to monitor the arguments that masque accepts when it’s masquerading as another function, we need a pick argument that can’t be confused with any other argument. The ES6 Symbol class is the perfect fit for the job. Let’s make a simple version of masque that only tracks whether or not it was called:

export const masque = () => {
    var called = false;
    return (...args) => {
        if(args.length == 1 && args[0] == masque.called)
            return called;
        else {
            called = true;
        }
    }
}

masque.called = Symbol('masque.called');

Now if we run var x = masque we have a callable function in the variable x. We can query x if has ever been called by invoking it with our special unique argument masque.called.

var x = masque();
x(masque.called); // false
x();
x(masque.called); // true

Recall that we wanted to be able to write expect(magic).wasCalled();. Now we know that magic is a value created by masque and that the wasCalled method of the Expectation class will look something like this:

wasCalled() {
    if(!this.value(masque.called))
        throw new Error('The function was never called.');
}

My complete implementation of masque logs all of the activity of an underlying function, fn, that you can optionally pass masque.

export const masque = (fn) => {
    var memos = new Array();
    if(!fn) fn = () => {};
    const wrapper = (...args) => {
        if(args.length == 1 && args[0] == masque.memos) {
            return memos;
        }

        let memo = { args: args };
        memos.push(memo);
        try {
            memo.result = fn(...args);
        } catch(error) {
            memo.error = error;
            throw error;
        }
        return memo.result;
    }
    wrapper.sigil = masque.sigil;
    return wrapper;
}

masque.memos = Symbol('masque.memos');
masque.sigil = Symbol('masque.sigil');

The masque.memos symbol let us interrogate all of the arguments the function received, and all of the things the underlying function returned. The symbol masque.sigil is a marker on the wrapper functions returned to distinguish masques from regular functions—this distinction is used when dispatching masques to the correct type of Expectation subclass.

Link to the GitHub repository

The point of earmarking this as “casual” is to emphasize that I think it’s a worthwhile exercise to build your own testing framework. Probably as long as you’re not building medical equipment or running critical infrastructure—but even the most reliable software started somewhere. That said, you can find my implementation on GitHub, because… vanity?

https://github.com/joeycarr/casualtesting