How to Mock redis datastore

Mocking and stubbing walk hand in hand. stubbing redis pub/sub, a datastore widely adopted in nodejs ecosystem, can be a setback when testing WebSocket endpoints. This article brings clarity, and a path forward, to it.

In this article we will talk about:

Even though this blog post was designed to offer complementary materials to those who bought my Testing nodejs Applications book, the content can help any software developer to tuneup working environment. You use this link to buy the book. Testing nodejs Applications Book Cover

Show me the code

module.exports = function(req, res, next){
  User.findById(req.user, (error, next) => {
    if(error) return next(error); 
    new Messenger(options).send().then((response) => {
      redisClient.publish(Messenger.SYSTEM_EVENT, payload));
      //schedule a delayed job 
      return res.status(200).json({message: 'Some Message'});
    });
  });
};

//service based equivalent using a service layer
module.exports = function(req, res, next){
  UserService.findById(req.user)
    .then(new Messenger(options).send())
    .then(new RedisService(redisClient).publish(Messenger.SYSTEM_EVENT, payload))
    .then(response => res.status(200).json(message);})
    .catch(error => next(error));
};

The use of arrow functions instead of function keyword serves to shorten the code. It is possible to replace all arrow functions with the function keywords, for readability.

What can possibly go wrong?

The following points may be a challenge to mock datastore access:

The following sections will explore more on making points stated above work.

Show me the tests

There is more than one way to go with mocking. I have to preview 3 libraries and choose one the fits better my needs.

Some of libraries are we can tap into to make mocking possible are : rewire, fakeredis, proxywire and sinon.

Mocking redis using rewire

var Rewire = require('rewire');
//module to mock redisClient from 
var controller = Rewire("/path/to/controller.js");
//the mock object + stubs
var redisMock = {
  //get|pub|sub are stubs that can return promise|or do other things
  get: sinon.spy(function(options){return "someValue";});
  pub: sinon.spy(function(options){return "someValue";});
sub: sinon.spy(function(options){return "someValue";});
};
//replacing --- `redis` client methods :::: this does not prevent spinup a new `redis` server
controller.__set__('redisClient', redisMock);

Example:

Mocking redis using fakeredis. fakeredis provides an thrown in replacement and functionalities for redis's createClient() function.

var redis = require("redis");    
var fakeredis = require('fakeredis'); 
var sinon = require('sinon'); 
var assert = require('chai').assert; 

var users, client; 
describe('redis', function(){
  before(function(){
    sinon.stub(redis, 'createClient', , fakeredis.createClient);
    client = redis.createClient(); //or anywhere in code it can be initialized
  });

  after(function(done){
    client.flushdb(function(error){
      redis.createClient.restore();
      done();
    });
  });
});

Example:

Two of the alternatives whose examples are not figuring in this article are mocking redis usingredis-mock and proxyquire.

The goal of the redis-mock project is to create a feature-complete mock of [`redisnode](https://github.com/mranney/node_redis), so that it may be used interchangeably when writing unit tests for code that depends onredis`_

Conclusion

In this article, we revisited strategies to mock redis access methods and replace response objects with mock data.

Testing in parallel can stress the redis server. Mocking redis clients makes tests faster, reduces friction on the network, and prevent stressing redis server especially when shared with other production applications.

There are additional complimentary materials in the “Testing nodejs applications” book.

References

#snippets #code #annotations #question #discuss