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?