Mockgoose: how to simulate a error in mongoose? - node.js

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
});

Related

Jest Testing of Redis Service

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'
});

Exporting a function declared inside asynchronous function

I have a file in which mongoose is setup
const keys = require('../chat/config/keys');
const mongoose = require('mongoose');
const dbURI = keys.mongoURI;
mongoose.connect(dbURI, { useNewUrlParser: true });
mongoose.set('debug', true);
let fetchVideo;
mongoose.connection.once('open', function () {
console.log('Mongoose default connection open to ' + dbURI);
let connectToDB = mongoose.connection.db;
let videoChatDB = connectToDB.collection('videochat');
fetchVideo = ({ id }) => {
if (id !== '100') {
videoChatDB.findOne({'studentID': parseInt(id)}).then((user) => {
if (user) {
console.log(user);
return true;
} else {
console.log(user);
return false;
}
});
}
}
});
module.exports = { fetchVideo };
And I am requiring that file inside my index.js file like so:
let db = require('./db');
In my index file I have a socket connection and I need to check the database when a new user comes.
socket.on('new-user', async (user) => {
let checkAvail = await db.fetchVideo(user);
});
But I am getting this error:
TypeError: db.fetchVideo is not a function
I am guessing it is undefined since it is declared inside an asynchronous function.
How would I make this to work?
Because the function is created asynchronously, one option is to export a Promise that resolves to fetchVideo function. Because mongoose.connection.once is callback-based, you'll have to transform it into a Promise.
If you want fetchVideo to resolve to something (rather than nothing), you also have to properly chain the findOne call with the promise chain; to fix that, return videoChatDB.findOne....
const fetchVideoProm = new Promise((res, rej) => {
mongoose.connection.once('open', function () {
console.log('Mongoose default connection open to ' + dbURI);
let connectToDB = mongoose.connection.db;
let videoChatDB = connectToDB.collection('videochat');
const fetchVideo = ({ id }) => {
if (id !== '100') {
return videoChatDB.findOne({'studentID': parseInt(id)}).then((user) => {
if (user) {
console.log(user);
return true;
} else {
console.log(user);
return false;
}
});
}
}
res(fetchVideo);
});
});
module.exports = { fetchVideoProm };
Consume it by awaiting the creation of the fetchVideo function, and then calling it:
socket.on('new-user', async (user) => {
const fetchVideo = await db.fetchVideoProm;
const checkAvail = await fetchVideo(user);
});

how to handle async call in lambda nodejs

I am creating a chess engine using nodejs in lambda but due to asynchronous call it showing timeout error on lambda every time. This is just partial part of the function. It is working fine on local nodejs console but not on the lambda. Please someone suggest something as I am new to this.
var chessjs = require('./chess');
var engine = require('uci');
var uciengine = new engine(process.env['LAMBDA_TASK_ROOT'] + '/stockfish');
var fs = require("fs");
var match;
function moveEngine() {
var curfen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
var depth = '20';
uciengine.runProcess().then(
() => {
console.log("Started.");
return uciengine.uciCommand();
}
).then(
() => {
console.log("Is Ready?");
return uciengine.isReadyCommand();
}
).then(
() => {
console.log("New game.");
return uciengine.uciNewGameCommand();
}
).then(
() => {
console.log("Setting position.");
return uciengine.positionCommand(curfen);
}
).then(
() => {
console.log('Starting position set');
console.log('Starting analysis');
return uciengine.depthLimitedGoCommand(depth, (info) => {
});
}
).then((bestmove) => {
console.log('Bestmove: ');
console.log(bestmove);
return uciengine.quitCommand();
}).then(() => {
console.log('Stopped');
response.sessionAttributes = {};
context.succeed(response);
}).done();
}
async call code
var chessjs = require('./chess');
var engine = require('uci');
var async= require('async');
var uciengine = new engine('/var/task/stockfish');
var fs = require("fs");
var match;
function moveEngine() {
var curfen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
var depth = '20';
async.auto({
runProcess: function(next, results) {
uciengine.runProcess(next,results);
},
checkUiEngineReady:['runProcess',function(next,results) {
uciengine.checkUiEngineReady(next,results);
}],
newGameCommand:['checkUiEngineReady',function(next,results) {
uciengine.newGameCommand(next,results);
}],
position:['newGameCommand',function(next,results) {
uciengine.positionCommand(curfen,next,results);
}],
godepth:['position',function(next,results) {
uciengine.depthLimitedGoCommand(depth,next,results);
}]
}, function(err, response) {
if (err) {
next(err);
} else {
console.log(response);
uciengine.quitCommand();
context.succeed(response);
}
});
}
moveEngine();
async call is giving the same error like before and i think it is probably wrong.
You can handle the Async call in Lambda using async npm module which is a utility module to handle asynchronous programming in Nodejs.
You can install the async module with npm install --save async.
async.auto function will be useful to manage the above call.
Here is an example through which you can manage your code.
async.auto({
runProcess: function(next, results) {
runProcess(next,results);
},
checkUiEngineReady:['runProcess',function(next,results) {
checkUiEngineReady(next,results);
}],
newGameCommand:['checkUiEngineReady',function(next,results) {
newGameCommand(next,results);
}]
}, function(err, response) {
if (err) {
next(err);
} else {
context.succeed(response);
}
});
Thanks

how to connect to mongodb synchronously in nodejs

I want to make use of the promises feature where in I can connect to mongodb synchronously and I can reuse the connection by passing it on to different modules.
Here is something that I came up with
class MongoDB {
constructor(db,collection) {
this.collection = db.collection(collection);
}
find(query, projection) {
if(projection)
return this.collection.find(query, projection);
else
return this.collection.find(query);
}
}
class Crew extends MongoDB {
constructor(db) {
super(db,'crews');
}
validate() {
}
}
I want to setup a connection somewhere in my initial code like the one below and then reuse the connection for different classes, just like how mongoose or monk does but using only the node-mongodb-native package.
MongoClient.connect(url)
.then( (err,dbase) => {
global.DB = dbase;
});
var Crew = new CrewModel(global.DB);
Crew.find({})
.then(function(resp) {
console.log(resp);
});
Right now, the db returns undefined inside the main MongoDB class and am not able to debug this one out through google or the documentation.
Edit: I had assumed that a promise was synchronous but that is not the case.
To reuse the connection I would create a module like this.
module.exports = {
connect: function(dbName, callback ) {
MongoClient.connect(dbName, function(err, db) {
_db = db;
return callback( err );
});
},
getDb: function() {
return _db;
}
};
After that you can connect to the database before starting your application
MongoConnection.connect("mongodb://localhost:27017/myDatabase", function(err){
app.listen(3000, function () {
// you code
});
});
Considering you created the module in a js file you can simply use require to get the databaseConnection
var dbConnection = require("./myMongoConnection.js");
and to get the connection use
var db = MongoConnection.getDb();
Another option using ES6 classes creates a singleton object that you can access repeatedly. It's inspired by #user3134009's answer here.
const EventEmitter = require('events');
const MongoClient = require('mongodb').MongoClient;
const config = require('config');
let _db = null;
class MongoDBConnection extends EventEmitter {
constructor() {
super();
this.emit("dbinit", this);
if (_db == null) {
console.log("Connecting to MongoDB...");
MongoClient.connect(config.dbs.mongo.url, config.dbs.mongo.options,
(err, db) => {
if (err) {
console.error("MongoDB Connection Error", err);
_db = null;
} else {
console.log("Connected to MongoDB", config.dbs.mongo.url);
db.on('close', () => { console.log("MongoDB closed", arguments); _db = null; });
db.on('reconnect', () => { console.log("MongoDB reconnected", arguments); _db = db; });
db.on('timeout', () => { console.log("MongoDB timeout", arguments); });
_db = db;
this.emit('dbconnect', _db);
}
});
}
}
getDB() {
return _db;
}
}
module.exports = new MongoDBConnection();
I have been struggling with this problem for a while, and in particular with setting up and persisting MongoDb connection in AWS lambda functions across invocations.
Thanks to #toszter answer I've finally come up with the following solution:
const mongodb = require('mongodb');
const config = require('./config.json')[env];
const client = mongodb.MongoClient;
const mongodbUri = `mongodb://${config.mongo.user}:${config.mongo.password}#${config.mongo.url}/${config.mongo.database}`;
const options = {
poolSize: 100,
connectTimeoutMS: 120000,
socketTimeoutMS: 1440000
};
// connection object
let _db = null;
class MongoDBConnection {
constructor() {}
// return a promise to the existing connection or the connection function
getDB() {
return (_db ? Promise.resolve(_db) : mConnect());
}
}
module.exports = new MongoDBConnection();
// transforms into a promise Mongo's client.connect
function mConnect() {
return new Promise((resolve, reject) => {
console.log('Connecting to Mongo...');
client.connect(mongodbUri, options, (error, db) => {
if (error) {
_db = null;
return reject(error);
}
else {
console.log('Connected to Mongo...');
_db = db;
resolve(db);
}
});
});
}
To use it in a controller or app.js:
const mongoConfig = require('mongoConfig');
mongoConfig.getDB()
.then(db => db.collection('collection').find({}))
.catch(error => {...});

How can I execute db.copyDatabase through NodeJS's MongoDB native driver?

I do have a shell script that invokes
mongo --eval "db.copyDatabase('somedatabase', 'somedatabase_duplicate', 'sourcehost')"
to copy a database.
Currently I am stuck with doing the same from within a Node.JS application. Calling
mongoCommand = `db.copyDatabase("somedatabase", "somedatabase_duplicate", "localhost")`;
db.command(mongoCommand, function(commandErr, data) {
if(!commandErr) {
log.info(data);
} else {
log.error(commandErr.errmsg);
}
});
Always resulsts in a "no such command" error message.
Edit for clarification: Using db.admin().command() results in the same problem and using the command suggested in enter link description here, too.
What's the correct way to call this command or, alternatively, to clone a database from Node.JS?
Well, you are trying to copy database which is administration operation so have to do with admin account. Again, to copy database command is copydb.
try running this command in shell, db.copyDatabase and you'll see source of command.
try:
var assert = require('assert');
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://localhost:27017/test';
MongoClient.connect(url, function(err, db) {
if (err) {
console.log(err);
}
else {
var mongoCommand = { copydb: 1, fromhost: "localhost", fromdb: "test", todb: "test_dup" };
var admin = db.admin();
admin.command(mongoCommand, function(commandErr, data) {
if (!commandErr) {
console.log(data);
} else {
console.log(commandErr.errmsg);
}
db.close();
});
}
});
//core modules
const assert = require('assert')
const MongoClient = require('mongodb').MongoClient;
const moment = require('moment');
const mongo = require('mongodb')
//custom modules
let { ip, port, database } = require('./dbUpgradeConfig')
const url = `mongodb://${ip}:${port}`
let todayDate = moment().format('DD/MM/YYYY HH:mm')
console.log(todayDate)
const myDate = new Date()
console.log(myDate)
var d = Date(Date.now());
// Converting the number of millisecond in date string
a = d.toString()
// Options for mongoDB
const mongoOptions = { useNewUrlParser: true }
let db
//TODO: handle reconnect
let connect = () => {
return new Promise((resolve, reject) => {
if (db) resolve()
else {
mongo.connect(url, mongoOptions, (err, client) => {
if (err) reject(err)
else {
db = client.db(database)
resolve()
}
})
}
})
}
/**
* #description create duplicate database from current database in mongodb
*/
let CloneDb = () => {
return new Promise((resolve, reject) => {
connect()
.then(() => {
console.log(db)
let mongoCommand = { copydb: 1, fromhost: "localhost", fromdb: "db_name", todb: "db_name_duplicate" }
let adminDB = db.admin()
adminDB.command(mongoCommand, function (commandErr, data) {
if (!commandErr) {
console.log(data)
} else {
console.log(commandErr.errmsg)
}
});
})
})
}
CloneDb().then(data => {
// debugger;
console.log("The clone db", data)
})

Resources