You probably know what
redis is. Just
in case it is a data structure server that usually work as a key value
pair store. The data is stored in memory by default instead of a disk,
so it is really really fast with the sacrifice of reliability.
Faking
At work while writing tests for some code that uses
redis, in
nodejs, I wanted to fake a
redis server. Why would I do
such thing? Because my tests are better isolated. They don't depend on
some other process (the
redis server).
Now how to fake the behavior of
redis for tests? By using
fantastic
fakeredis.
fakeredis is an
npm library which makes faking the
redis behavior in a
nodejs application very easy.
It also has a ruby gem, so can be used in a ruby application as
well.
The code under tests
This is the code I wanted to test.
var redis = require('redis');
var client = redis.createClient();
function isOnline(username, callback){
client.exists(username + 'SocketId', function(err, exists){
callback(err, !!exists);
});
}
exports.isOnline = isOnline;
isOnline is a simple function that checks whether a socket id
for a given user name exists in the
redis store.
Tests Without Faking
My usual test for this function, if I didn't knew
fakeredis existed, would be,
var redis = require('redis');
var assert = require('assert');
var users = require('../lib/users.js')
var client = redis.createClient();
describe('isOnline', function(){
it('should return true if user has a socket id', function(done){
//add a mock key value pair
client.set('onlineUserSocketId', 'some_socket_id', function(){
users.isOnline('onlineUser', function(err, online){
assert(online);
done();
});
});
});
it('should return false if user does not have a socket id', function(done){
users.isOnline('offlineUser', function(err, online){
assert(!online);
done();
});
});
});
As it is obvious by the code itself I am testing whether the function
returns true to the callback when a socket id for a particular user
name is in the
redis store, and false otherwise.
The biggest problem here is that anyone who runs those tests must make
sure a
redis-
server process is running. Tests won't pass
otherwise. Also running these tests on the production server is
probably a bad idea since they would tamper with production data. Also
after each test their should be a clean up step that clears out the
data added to the database during the test. A
flushdb call,
which flushes out the data, would be ideal for this task. As every
test would start to run on a clean database, they will be better
isolated from each other. But such call is hazardous because someone
could run the tests on a production server and flush out all the
production data!
Tests With faking
Here's how
fakeredis is used to avoid above shortcomings.
I used
sinon to stub the
redis createClient function.
Before all here is the before hook that does it.
var fakeredis = require('fakeredis');
var sinon = require('sinon');
var assert = require('assert');
var users;
var client;
describe('isOnline', function(){
before(function(){
sinon.stub(redis, 'createClient', fakeredis.createClient);
users = require('../lib/users.js');
client = redis.createClient();
});
});
The block of code in the before hook would run before running any test
and would stub the regulare
redis.createClient function with
fakeredis.createClient function. Stubbing is replacing a
resource that your system uses, with something that imitates that
resource's behavior. So after stubbing whenever the
redis.createClient is called what actually gets called is
fakeredis.createClient.
Now any method call on the
redis client instance returned by
createClient function would not tamper with anything in the
redis server.
fakeredis would imitate the behavior of
and the code under tests would not know a thing about the faking! Now
the tests look like this.
var redis = require('redis');
var fakeredis = require('fakeredis');
var sinon = require('sinon');
var assert = require('assert');
var users;
var client;
describe('isOnline', function(){
before(function(){
sinon.stub(redis, 'createClient', fakeredis.createClient);
users = require('../lib/users.js');
client = redis.createClient();
})
after(function(){
redis.createClient.restore();
});
afterEach(function(done){
client.flushdb(function(err){
done();
});
})
it('should return true if user has a socket id.', function(done){
client.set('onlineUserSocketId', 'some_socket_id', function(){
users.isOnline('onlineUser', function(err, online){
assert(online);
done();
});
});
});
it('should return false if user does not have a socket id.', function(done){
users.isOnline('offlineUser', function(err, online){
assert(!online);
done();
});
});
});
As you can see I have added an
after and an
afterEach block. What the
after block does should be
obvious. It brings the
redis.
createClient function to
the previous state.
The
afterEach block is more interresting. It adds the clean up
step I stated earlier. It calls
client.flushdb without
hesitation because the the client object would only work with
fakeredis data. As the name implies
afterEach hook is
called after each test so that it makes sure the next test is run on a
fresh store.