Question

With help of another question and the relevant QUnit documentation I can create unit tests that deal with the async nature of throttled KnockoutJS observables.

However, I haven't yet found an elegant way to have both Red and Green tests behave nicely in both test runners I use:

  • the QUnit browser based test runner
  • the Visual Studio test runner (combined with Chutzpah to run JavaScript tests)

Suppose the following View Model:

var Person = function(firstName, surname) {
    var self = this;

    self.firstName = ko.observable(firstName);
    self.surname = ko.observable(surname);

    self.fullName = ko.computed({
        write: function(val) {
            var parts = val.split(" ");
            self.firstName(parts[0]);
            self.surname(parts[1]);
        },
        read: function() { return self.firstName() + " " + self.surname(); }
    }).extend({throttle: 20});
};

And suppose these two basic tests:

test("Can read full name", function(){
    var john = new Person("John", "Doe");
    strictEqual(john.fullName(), "John Doe");
});


test("Can write full name", function(){
    var person = new Person();
    person.fullName("John Doe");
    strictEqual(person.firstName(), "John");
    strictEqual(person.surname(), "Doe");
});

The latter will fail. This makes sense, because the asserts run instantly, whereas the actual update for the fullName call runs later: it is delayed by 20 ms by the throttle. Without throttle things succeed.

No problem, from linked question I learned that I can use an async test and use a manual subscription to make things green again:

asyncTest("Can write full name", function(){
    var person = new Person();

    expect(2);

    person.surname.subscribe(function(val) {
        start();
        strictEqual(person.firstName(), "John");
        strictEqual(person.surname(), "Doe");
    });    

    person.fullName("John Doe");
});

Now, suppose I break my View Model like this:

// self.surname(parts[1]); // "bug" introduced to demonstrate the problem

Then the test runner will hang. I can "fix" this issue by resuming my test after -say- 2 seconds no matter what by ending my test with this:

// After 2 secs we assume the test failed
setTimeout(function() {
    start();
}, 2000);

This works in the browser based test runner for the bugged code, but has an error on the correct code, from the console:

pushFailure() assertion outside test context, was ... at QUnit.start

Makes sense, because now start() is called twice. My first intuition was to do a check in the setTimeout callback to see the "trancount" (i.e. ask QUnit if I need to start or not), but QUnit has no support for this (probably for a reason :D).

Anyways, to sum up all possible situations:

  • Green because everything is okay.
  • Red because the subscription never fired.
  • Red because the assertion failed.

How to structure the test so that all situations are accounted for and both test runners will react nicely?

Was it helpful?

Solution

Found at least one solution to this problem while writing the question. Posting the question along with my answer (a) just in case it may help others and/or (b) other folks may have better solutions for this.

Bottom line is to clear the setTimeout return value as soon as the subscription fires:

asyncTest("Can write full name", function(){
    var person = new Person();

    // After 2 secs we assume the test failed
    var timeout = setTimeout(function() {
        start();
    }, 2000);

    expect(2);

    person.surname.subscribe(function(val) {
        window.clearTimeout(timeout);
        start();
        strictEqual(person.firstName(), "John");
        strictEqual(person.surname(), "Doe");
    });

    person.fullName("John Doe");
});

This works for all scenarios:

Both red cases have a nice assertion failure.

Only downside to this solution is that it's a bit verbose, you need 4 extra lines of testing-plumbing. (Perhaps someone else has an answer utilizing a feature of QUnit for this purpose?)

Also, one other downside: if you increase the throttle so it is above the timeout the test first goes red but QUnit will crash a bit later too because start() is called once more.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top