0

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();
  });
});
josiah
  • 1,296
  • 1
  • 11
  • 29
  • Realizing that to get what I want, I'll have to stub `redisClient.get` to inject the value(s) I expect for each call to it if I want to test the arguments going to the `redisCallback` function. That brings up the problem that I haven't been able to successfully stub any of the redisClient methods, yet. http://stackoverflow.com/questions/33241089/how-to-spy-on-a-stubbed-objects-method – josiah Oct 21 '15 at 18:05
  • I've figured out how to stub node_redis functions, now, but the problem remains that I can't correctly spy on any functions executed within a Node Async callback without stubbing the async function that uses the callback. Ideally, my test shouldn't have to know about the implementation detail that the function I want to verify was called is called within an async callback. What's the best way to test this? Is there a way to avoid stubbing the `redisClient.get` method if I want to test that `callback` was called in the above code?? – josiah Oct 23 '15 at 15:38

0 Answers0