14

I am working in node.js. My app interacts with Redis via the node_redis module. I'm using mocha and sinon to automate testing of my app. My app looks something like this:

...snip
var redisClient = redis.createClient(redisPort, redisHost);
var someValue = redisClient.get("someKey");
return someValue;
....

I want to stub the call to redisClient.get(). To do this I also need to stub the call to redis.createClient() - I think... Here's my test code:

...
var redis = require("redis");
var redisClient;
...
sinon.stub(redisClient, 'get').returns("someValue");
sinon.stub(redis, "createClient").returns(redisClient);
...
assert.equal(redis_client_underTest.call_to_redis(), "someValue");
...

The test fails with AssertionError: false == "someValue"

How do I stub out redisClient, or is this even possible?

nha
  • 17,623
  • 13
  • 87
  • 133
Rich
  • 658
  • 1
  • 9
  • 18

6 Answers6

12

What you could do is use something like Proxyquire or Rewire. I'll be using rewire for the example.

Your snippet of code you want to stub:

var redisClient = redis.createClient(redisPort, redisHost);
var someValue = redisClient.get("someKey");
return someValue;

Then in your test you can use rewire:

var Rewire = require('rewire');

var myModule = Rewire("../your/module/to/test.js");

var redisMock = {
    get: sinon.spy(function(something){
             return "someValue";
         });
};

myModule.__set__('redisClient', redisMock);

This way you can have your redisClient replaced and you can check with the spy if the function was called.

Osukaa
  • 708
  • 3
  • 10
  • 22
2

Few key points are:

  • Stub redisClient before loading main module.
  • Stub the CRUD methods for redisClient.

Below is my main module:

/* Main module */
const redis = require('redis');
const redisClient = redis.createClient();

function value(){
  return redisClient.get('someKey');
}

module.exports = {value: value}

Below is my test script:

/* Test */
var sinon = require('sinon');
var redis = require('redis');

// stub redis.createClient

var redisClient = {
    'get': () => "someValue"
}

var redisGetSpy = sinon.spy(redisClient, "get");
var redisClientStub = sinon.stub(redis,
                                 "createClient").callsFake(() => redisClient);

// require main module
var main = require('./main.js');

console.log(main.value(),
            redisClientStub.called,
            redisGetSpy.called, 
            redisGetSpy.callCount);
Tushar Gautam
  • 458
  • 6
  • 19
1

You can also use something like redis-mock which is a drop in replacement for node_redis that runs in memory.

Use rewire (or Jest module mocks or whatever) to load the mock client in your tests instead of the real client and run all your tests against the in-memory version.

Tamlyn
  • 22,122
  • 12
  • 111
  • 127
  • IMO this is a much thorough testing solution because it tests actual app logic that is based on what redis is expected to return rather than simply checking that some stub function is called. Plus you can rely on the tests within the mocked redis package to prove it is mocking correctly. – Phil Jul 09 '23 at 09:58
1

The other way is to do in your class static function getRedis and mock it. For example:

let redis = {
   createClient: function() {},
};
let connection = {
   saddAsync: function() {},
   spopAsync: function() {},
};
let saddStub = sinon.stub(connection, 'saddAsync');
sinon.stub(redis, 'createClient').returns(connection);
sinon.stub(Redis, 'getRedis').returns(redis);

expect(saddStub).to.be.calledOnce;

Inside your class connect function looks like:

this.connection = YourClass.getRedis().createClient(port, host, optional);
0

This initially seemed very trivial to me, but I was faced with a lot of caveats, particularly because I was using jest for testing, which does not supports proxyquire. My objective was to mock the request dependency of redis, so here's how I achieved it:

// The objective is to mock this dependency
const redis = require('redis');

const redisClient = redis.createClient();
redis.set('key','value');

Mocking dependency with rewiremock:

const rewiremock = require('rewiremock/node');

const app = rewiremock.proxy('./app', {
  redis: {
    createClient() { 
      console.log('mocking createClient');
    },
    set() {
      console.log('mocking set');
    }
  },
});

The catch is that you can't use rewire, or proxyquire with jest, that you need a library like rewiremock to mock this dependency.

REPL example

Menelaos Kotsollaris
  • 5,776
  • 9
  • 54
  • 68
0
const sinon = require('sinon');
const redis = require('redis');
const { mapValues, isObject, isUndefined, noop, isFunction } = require('lodash');

let redisStub;
let redisStore = {};

const removeKeyWhenExpired = (db, key, expirationSec) => {
    setTimeout(() => {
        redisStore[db || 0][key] = undefined;
    }, expirationSec * 1000);
};

sinon.stub(redis, 'createClient').callsFake(() => ({
    on: () => { },
    select: (db) => {
        this.db = db;
        redisStore[db] = {};
    },
    get: (key, callback) => callback(null, redisStore[this.db || 0][key]),
    set: (key, value, ...args) => {
        redisStore[this.db || 0][key] = isObject(value) ? JSON.stringify(value) : value;
        if (args[0] === 'EX') {
            removeKeyWhenExpired(this.db, key, args[1]);
        }
        const callback = args[args.length - 1] || noop;
        if (isFunction(callback)) {
            callback(null, true);
        }
    },
    del: (key, callback = noop) => {
        if (!isUndefined(redisStore[this.db || 0][key])) {
            redisStore[this.db || 0][key] = undefined;
            return callback(null, 1);
        }
        return callback(null, 0);
    },
    expireat: (key, exp, callback = noop) => {
        removeKeyWhenExpired(this.db, key, exp - (new Date().getTime() / 1000));
        return callback(null);
    },
}));
Peshi Bloy
  • 66
  • 5