Problem summary: I want to test that redisCallback(), which is called inside of a NodeJS Async callback (see the code below), has been called with the expected arguments the expected number of times.
background information (skip if TL;DR):
I searched around but only found a solution for QUnit. I'm not sure what the equivalent for Mocha + Sinon would be for this predicament.
In my current test, the Chai assertion responds with AssertionError: expected false to equal true.
However, I tested this by calling this.redisCallback inside of the updateRedis function (which is under test), but outside of the redisClient.get async callback, and in this case, the test suite properly detects that the callback has be fired and my test succeeds.
What this tells me is that this is an Async issue. How do I tell Mocha/Chai to wait to check the assertion until after the redisClient.get callback has finished?
Code
This code was written to solve an issue with Node where I needed to check if a key in Redis was different than a value returned from a system command, and if it's changed, set the new value in Redis. Since the system command value would be in the asynchronous call's encompassing scope (and therefore, inaccessible/unreliable), I created a function that returns the callback I want for the redisClient.get callback, but passes the key used and the system command value into a closure to memoize the values for the callback.
Something close to what I have written using HighlandJS:
var redisClient = require('redis').createClient(config.redis.port, config.redis.host),
_ = require('highland');
// ...
redisCallback = function (resultsHash, redisKey, redisGetValue) {
var systemCmdValue = resultsHash[redisKey];
if (redisGetValue != systemCmdValue) {
redisClient.set(redisKey, systemCmdValue, function (err) { // handle errors });
}
};
// ...
updateRedis = function (err, stdout, stderr) {
// Do some stuff that's not important to this problem
var resultsHash = parseCmdResults(stdout),
redisKeys = Object.keys(resultsHash),
curriedRedisCallback = _.curry(this.redisCallback, resultsHash);
get = _.wrapCallback(redisClient.get.bind(redisClient));
redisKeys.each(function (key) {
get(key).each(curriedRedisCallback(key));
});
};
An equivallent implementation in vanilla NodeJS and JavaScript:
var redisClient = require('redis').createClient(config.redis.port, config.redis.host);
// ...
redisCallback = function (resultsHash, redisKey, redisGetValue) {
var systemCmdValue = resultsHash[redisKey];
if (redisGetValue != systemCmdValue) {
redisClient.set(redisKey, systemCmdValue, function (err) { // handle errors });
}
};
redisCallbackWrapper = function (resultsHash) {
return function (redisKey) {
return function (redisGetValue) {
this.redisCallback(resultsHash, redisKey, redisGetValue);
};
};
};
// ...
updateRedis = function (err, stdout, stderr) {
// Do some stuff that's not important to this problem
var resultsHash = parseCmdResults(stdout),
redisKeys = Object.keys(resultsHash),
callback = redisCallbackWrapper(resultsHash),
key;
for (var i = 0; i < redisKeys; i++) {
key = redisKeys[i];
redisClient.get(key, callback(key));
}
};
My test using Mocha, Chai, and Sinon:
describe('when valid arguments are provided to updateRedis', function () {
var err, stdout, stderr, parsedStdout, key0, key1;
before(function () {
err = undefined;
stderr = undefined;
stdout = 'key0/value0\nkey1/value1';
key0 = 'key0';
key1 = 'key1';
parsedStdout = {};
parsedStdout[key0] = "value0";
parsedStdout[key1] = "value1";
sinon.spy(myModule, 'redisCallback');
});
after(function () {
myModule.redisCallback.restore();
});
it('calls redisCallback with the expected arguments the expected number of times', function (done) {
myModule.updateRedis(err, stdout, stderr);
// If I can get to the point where it's detected that redisCallback has been called at all,
// I can follow the documentation to figure out how to test that it's called with the expected args the expected number of times.
expect(myModule.redisCallback.called).to.equal(true);
done();
});
});