Jest Testing of Redis Service - node.js

I have a number of services in my Nodejs repo that uses Redis. I'm trying to improve the quality of my development and therefore am starting to implement Jest testing. However, I cannot get the Jest tests to connect to the (test) Redis database during testing.
I've tried using a continually running Redis server on the CI/CD (Jenkins) server and I have tried using this library "Redis-Memory-Server" which is meant to create a Redis server instance during testing. I've spent many hours this week trying to fix this issue and have no idea why it's happening.
Any help is greatly appreciated.
Redis Management File
// Redis database module for Unseen Games
const redis = require('redis');
const { promisifyAll } = require('bluebird');
const _ = require('lodash');
promisifyAll(redis);
//Contains all the redis clients currently made
var event_status = "unconnected";
var timeout_cleared = false;
const clients: any = {};
let connectionTimeout;
function throwTimeoutError() {
connectionTimeout = setTimeout(() => {
throw new Error('Redis connection failed');
}, 10000);
}
function instanceEventListenersRedis({ conn }) {
conn.on('connect', () => {
// console.log('CacheStore - Connection status: connected');
event_status = "connected";
timeout_cleared = false;
clearTimeout(connectionTimeout);
});
conn.on('ready', () => {
event_status = "ready";
// console.log('CacheStore - Connection status: ready');
})
conn.on('end', () => {
event_status = "disconnected";
// console.log('CacheStore - Connection status: disconnected');
//TODO: The below code should stop Jest from hanging when testing the code, but doesn't, fix?
// if(!timeout_cleared) {
// throwTimeoutError();
// }
});
conn.on('reconnecting', () => {
event_status = "reconnecting";
// console.log('CacheStore - Connection status: reconnecting');
clearTimeout(connectionTimeout);
});
conn.on('error', (err) => {
event_status = "error";
// console.log('CacheStore - Connection status: error ', { err });
throwTimeoutError();
});
}
export const redisInit = async () => {
if(process.env.BALENA==="1" || process.env.DEVICE === "local"){
const cacheInstance = redis.createClient(process.env.REDIS_URL);
clients.cacheInstance = cacheInstance;
instanceEventListenersRedis({ conn: cacheInstance });
} else if(process.env.DEVICE =="demo") {
event_status = "connecting"
const cacheInstance = redis.createClient({host: process.env.REDIS_HOST, port: process.env.REDIS_PORT});
clients.cacheInstance = cacheInstance;
instanceEventListenersRedis({ conn: cacheInstance });
} else {
throw Error;
}
};
export const redisCheckConnection = () => {
if(process.env.REDIS == "true") {
return event_status;
} else {
return "readyo";
}
}
export const redisGetClients = () => clients;
export const redisCloseConnections = () => {
timeout_cleared = true;
_.forOwn(clients, (conn) => conn.quit());
}
jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFiles: ['dotenv/config'],
transform: {}
};
process.env = Object.assign(process.env, {
REDIS: 'true',
DEVICE: 'demo'
});

Related

IOredis Connection is Closed error in Nodejs

IOredis Connection is Closed error in Nodejs. We are using ioredis npm, When we are trying to getting the data from redis, we are getting "Connection is Closed" errors.
Actually, in calling the hnormalget() function is giving Connection is Closed error.
var redis = require('ioredis');
var config = require('./../../config');
var redisConfig = config.database;
var clientRead = Promise.promisifyAll(redis.createClient({
port: config.database.redisPort,
host: config.database.redisHost,
prefix: config.database.redisPrefix,
password: config.database.redisPassword,
connectionName: 'redisDB-gpRedisCacheRead',
db:10,
readOnly: true,
keepAlive: 1,
retryStrategy : function retryStrategy(times) {
log.error("Redis retry strategy called for gpRedisCache.js...44")
if (typeof times != "number") {
// Manual Reconnect would be require
// redis.connect()
return new Error('Redis Retry time exhausted');
}
const delay = Math.min(times * 50, 2000);
return delay;
}
}));
var DBNUM = config.redis.GPDatabaseNum;
clientRead.on('connect', function() {
clientRead.select(DBNUM, function(e, r) {
if (e) {
log.error(`Error in redis Read select db server/lib/gpRedisCache.js: ${e}`);
clientRead.end(true); //also initiate reconnection
} else {
// console.log('clientRead connected');
}
});
}); //end of client.on('connect')
clientRead.on('error', function(err) {
log.error(`Error in redis connection on file gpRedisCache.js:95: ${err}`);
clientRead.end(true); //also initiate reconnection here
});
var redisCacheSeparateReadWritePooledConn = {
hnormalget: (hashKey, key, cb) => {
return clientRead.hget(hashKey, key).then((data) => {
return { err: null, data };
}).catch(err => {
return { err };
});
},
}
//TILL above is the handling for single conn
module.exports = redisCacheSeparateReadWritePooledConn;

How to mock Tedious Module SQL Connection functions in JEST

I am using Azure functions written in Nodejs.
I have logic to insert into DB after all actions are completed. This gets called from main index.js after some api calls. So, from test class im expecting to mock database methods. and cant understand mocking much!
Below is the code for Database logic.
'use strict';
const { Connection, Request, TYPES } = require('tedious');
const config = {
server: process.env.myDB_Server,
authentication: {
type: 'default',
options: {
userName: process.env.myDB_User,
password: process.env.myDB_Pwd
}
},
options: {
encrypt: true,
database: process.env.myDB_Name
}
};
const myDB = process.env.myDB;
module.exports = async(context, myPayload, last_Modified_By, status, errorCode, errorMsg, errorDescription) => {
try {
context.log('inside azureTable function');
let connection = new Connection(config);
connection.on('connect', function(err1) {
if (err1) {
context.log('Error connection.OnConnect to DB:::', err1.message);
//logger.error('Error connection.OnConnect to DB::', err1);
let dbStatus = {};
dbStatus["status"] = 400;
dbStatus["message"] = err1.message;
context.res.body["dbStatus"] = dbStatus;
context.done();
} else {
context.log('Database Connection Successful.');
var request = new Request("INSERT INTO " + myDB + " (Correlation_Id,Created_Date,LastModified_Date,Last_Modified_By,Status_CD,Error_Code,Error_Msg,Error_Description,Payload) VALUES (#correlationId,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,#Last_Modified_By,#Status_CD,#Error_Code,#Error_Msg,#Error_Description,#Payload);", function(err2) {
if (err2) {
context.log('Error inserting records to DB::', err2.message);
//logger.error('Error inserting records to DB::' + err2.message);
let dbStatus = {};
dbStatus["status"] = 400;
dbStatus["message"] = err2.message;
context.res.body["dbStatus"] = dbStatus;
context.done();
}
});
request.addParameter('correlationId', TYPES.NVarChar, JSON.parse(myPayload).correlationId);
request.addParameter('Last_Modified_By', TYPES.NVarChar, last_Modified_By);
request.addParameter('Status_CD', TYPES.NVarChar, status);
request.addParameter('Error_Code', TYPES.Int, errorCode);
request.addParameter('Error_Msg', TYPES.NVarChar, errorMsg);
request.addParameter('Error_Description', TYPES.NVarChar, errorDescription);
request.addParameter('Payload', TYPES.NVarChar, myPayload);
// Close the connection after the final event emitted by the request, after the callback passes
request.on("requestCompleted", function(rowCount, more) {
context.log('Records Successfully inserted into DB');
connection.close();
let dbStatus = {};
dbStatus["status"] = 201;
dbStatus["message"] = "Records Successfully inserted into DB";
context.res.body["dbStatus"] = dbStatus;
context.done();
});
connection.execSql(request);
}
});
connection.connect();
} catch (err) {
context.log('Error in main function::', err.message);
//logger.error('Error in main function::' + err.message);
let dbStatus = {};
dbStatus["status"] = 400;
dbStatus["message"] = err.message;
context.res.body["dbStatus"] = dbStatus;
context.done();
}
};
How can i mock the connection.on connect or request = new Request without actually hitting DB ?
I tried this, but its going to actual connection.
index.test.js
test('return 500 when db connection fails" ', async() => {
const tedious = require('tedious');
const connectionMock = jest.spyOn(tedious, 'connect');
connectionMock.mockImplementation(() => {
return {
}
});
//calling index js
}, 15000);
test('return 500 when db connection fails" ', async() => {
const tedious = require('tedious');
const connectionMock = jest.spyOn(tedious, 'Connection');
connectionMock.mockImplementation(() => {
{
throw new Error('some err');
}
});
//calling index js
}, 15000);
After going through some docs, tried below with no luck. Jest is not setting return value and gets timed out.
jest.mock('tedious', () => ({
Connection: jest.fn(() => ({
connect: jest.fn().mockReturnValue('err'),
on: jest.fn().mockReturnValue('err')
}))
}))
/* jest.mock('tedious', () => ({
Connection: jest.fn(() => ({
connect: jest.fn(() => (connect, cb) => cb(null)),
on: jest.fn(() => (connect, cb) => cb('err'))
}))
})) */
Finally I figured it out.
Issue was the mocking params not being set correctly. Unnecessarily used Jest.fn() for inner methods which actually doesn't help.
Here is the final solution:
jest.mock('tedious', () => ({
Connection: jest.fn(() => ({
connect: () => {},
on: (connect, cb) => cb(),
close: () => {},
execSql: () => {}
})),
TYPES: jest.fn(),
Request: jest.fn(() => ({
constructor: (sqlString, cb) => cb('err', null, null),
addParameter: (name, type, value) => {},
on: (requestCompleted, cb) => cb('rowCount', 'more')
}))
}))

IOredis Connection timeout error in Nodejs

IOredis Connection timeout error in Nodejs.
We are using ioredis npm, When we are trying to getting the data from redis, we are getting "Connection is Closed" errors.
Actually, in calling the hnormalget() function is giving Connection is Closed error.
var redis = require('ioredis');
var config = require('./../../config');
var redisConfig = config.database;
var clientRead = Promise.promisifyAll(redis.createClient({
port: config.database.redisPort,
host: config.database.redisHost,
prefix: config.database.redisPrefix,
password: config.database.redisPassword,
connectionName: 'redisDB-gpRedisCacheRead',
db:10,
readOnly: true,
keepAlive: 1,
retryStrategy : function retryStrategy(times) {
log.error("Redis retry strategy called for gpRedisCache.js...44")
if (typeof times != "number") {
// Manual Reconnect would be require
// redis.connect()
return new Error('Redis Retry time exhausted');
}
const delay = Math.min(times * 50, 2000);
return delay;
}
}));
var DBNUM = config.redis.GPDatabaseNum;
clientRead.on('connect', function() {
clientRead.select(DBNUM, function(e, r) {
if (e) {
log.error(`Error in redis Read select db server/lib/gpRedisCache.js: ${e}`);
clientRead.end(true); //also initiate reconnection
} else {
// console.log('clientRead connected');
}
});
}); //end of client.on('connect')
clientRead.on('error', function(err) {
log.error(`Error in redis connection on file gpRedisCache.js:95: ${err}`);
clientRead.end(true); //also initiate reconnection here
});
var redisCacheSeparateReadWritePooledConn = {
hnormalget: (hashKey, key, cb) => {
return clientRead.hget(hashKey, key).then((data) => {
return { err: null, data };
}).catch(err => {
return { err };
});
},
}
//TILL above is the handling for single conn
module.exports = redisCacheSeparateReadWritePooledConn;

share mqtt client object between files

I connect to MQTT this way:
//mqtt.js
const mqtt = require('mqtt');
var options = {
//needed options
};
var client = mqtt.connect('mqtt://someURL', options);
client.on('connect', () => {
console.log('Connected to MQTT server');
});
I want to export the client object this way:
//mqtt.js
module.exports = client;
So that I can import it in other files and make use of it this way:
//anotherFile.js
const client = require('./mqtt');
client.publish(...)
However, we all know that this will not work! How can I achieve this ?
Update
I tried promise and get a very strange behavior. When I use the promise in the same file (mqtt.js) like the code below, everything is OK:
//mqtt.js
const mqtt = require('mqtt');
var mqttPromise = new Promise(function (resolve, reject) {
var options = {
//needed options
};
var client = mqtt.connect('mqtt://someURL', options);
client.on('connect', () => {
client.subscribe('#', (err) => {
if (!err) {
console.log('Connected to MQTT server');
resolve(client);
} else {
console.log('Error: ' + err);
reject(err);
}
});
});
});
mqttPromise.then(function (client) {
//do sth with client
}, function (err) {
console.log('Error: ' + err);
});
But when I export the promise and use it in another file, like this:
//mqtt.js
//same code to create the promise
module.exports = mqttPromise;
//anotherFile.js
const mqttPromise = require('./mqtt');
mqttPromise.then(function (client) {
//do sth with client
}, function (err) {
console.log('Error: ' + err);
});
I get this error:
TypeError: mqttPromise.then is not a function
You can probably achieve your goal creating 2 files, one for handling mqtt methods and another to manage the connection object.
Here's the file for the mqtt handler:
//mqttHandler.js
const mqtt = require('mqtt');
class MqttHandler {
constructor() {
this.mqttClient = null;
this.host = 'YOUR_HOST';
this.username = 'YOUR_USER';
this.password = 'YOUR_PASSWORD';
}
connect() {
this.mqttClient = mqtt.connect(this.host, {port: 1883});
// Mqtt error calback
this.mqttClient.on('error', (err) => {
console.log(err);
this.mqttClient.end();
});
// Connection callback
this.mqttClient.on('connect', () => {
console.log(`mqtt client connected`);
});
this.mqttClient.on('close', () => {
console.log(`mqtt client disconnected`);
});
}
// // Sends a mqtt message to topic: mytopic
sendMessage(message, topic) {
this.mqttClient.publish(topic, JSON.stringify(message));
}
}
module.exports = MqttHandler;
Now lets use the exported module to create a mqtt client connection on another file:
//mqttClient.js
var mqttHandler = require('./mqttHandler');
var mqttClient = new mqttHandler();
mqttClient.connect();
module.exports = mqttClient;
With this exported module you can now call your client connection object and use the methods created in the mqttHandler.js file in another file :
//main.js
var mqttClient = require('./mqttClient');
mqttClient.sendMessage('<your_topic>','<message>');
Although there may be a better method to perform your task, this one worked pretty well for me...
Hope it helps!
cusMqtt.js
const mqtt = require("mqtt");
function prgMqtt() {
const options = {
port: 1883,
host: "mqtt://xxxxxxx.com",
clientId: "mqttjs_" + Math.random().toString(16).substr(2, 8),
username: "xxxxxx",
password: "xxxxxx",
keepalive: 60,
reconnectPeriod: 1000,
protocolId: "MQIsdp",
protocolVersion: 3,
clean: true,
encoding: "utf8",
};
prgMqtt.client = mqtt.connect("mqtt://xxxxxxxx.com", options);
prgMqtt.client.on("connect", () => {
prgMqtt.client.subscribe("Room/Fan");
console.log("connected MQTT");
});
prgMqtt.client.on("message", (topic, message) => {
console.log("message is " + message);
console.log("topic is " + topic);
// client.end();
});
}
exports.prgMqtt = prgMqtt;
index.js/main program call
const { prgMqtt } = require("./startup/cusMqtt");
prgMqtt();
another .js
const { prgMqtt } = require("../startup/cusMqtt");
router.get("/:id", async (req, res) => {
prgMqtt.client.publish("Room/Reply", "Replied Message");
});

Mockgoose: how to simulate a error in mongoose?

I am trying to do unit test around a mongoose powered application.
While mockgoose does a great work at simulating mongoose so I can test around it, I didn t find a way to push it to fail a call, so I can test the error handling logic.
Is it a supported use case? Or should I find another framework?
Code:
var mongoose = require('mongoose');
var Test = {},
Doc = require('./model/Doc.js');
var dbURL = 'mongodb://127.0.0.1/',
dbName = 'Test';
function connect(callback) {
Test = mongoose.createConnection(dbURL + dbName); //<-- Push this to fail
Test.on('error', (err) => {
callback(err);
});
Test.once('open', function () {
callback();
});
}
Test:
var chai = require('chai'),
expect = chai.expect,
util = require('util');
var config = require('./config.json');
var proxyquire = require('proxyquire').noPreserveCache();
var sinon = require('sinon');
var mongoose = require('mongoose');
var mockgoose = require('mockgoose');
describe('test', () => {
describe('Connect', () => {
beforeEach((callback) => {
mockgoose(mongoose).then(() => {
callback();
});
});
it('Expect to connect to the database', (done) => {
var stub = {
mongoose: mongoose
},
test = proxyquire('./../test.js', {
'mongoose': stub.mongoose
});
test.connect((err) => {
try {
expect(err).to.not.be.ok;
done();
} catch(err) {
done(err);
}
});
it('Expect to throw error when failing to connect to the database', (done) => {
var stub = {
mongoose: mongoose
},
medical = proxyquire('./../medical.js', {
'mongoose': stub.mongoose
});
medical.connect((err) => {
try {
expect(err).to.be.ok;
done();
} catch(err) {
done(err);
}
});
});
});
});
Result in:
Connect
✓ Expect to connect to the database
1) Expect to throw error when failing to connect to the database
1 passing (234ms)
1 failing
1) Medical Connect Expect to throw error when failing to connect to the database:
AssertionError: expected undefined to be truthy
I ended up stubing mongoose.createConnection (so the generator) to return a object which call the error.
let stub = {
mongoose: {
createConnection: () => {
return {
on: (eventName, callback) => {
if(eventName === 'error') {
callback('Medical Error');
}
},
once: () => {}
}
}
}
},
medical = proxyquire('./../medical.js', {
'mongoose': stub.mongoose
});

Resources