Module under test:
'use strict';
const config = require('config');
const q = require('q');
class RedisAccess {
static getValue(key) {
let deferred = q.defer();
if (config.redis.disableInteraction) {
deferred.resolve();
return deferred.promise;
}
config.redisClient.get(key, function handleResults(err, result) {
...
return deferred.promise;
}
}
exports = module.exports = RedisAccess;
Test:
var proxyquire = require('proxyquire').noPreserveCache();
var assert = require('assert');
var readdirError = new Error('some error');
var redisClientStub = { };
var calledBack;
// Override redisClient used by RedisAccess.js.
var redisClientProxy = proxyquire('../../../lib/data/redis/RedisAccess.js', { 'config' : redisClientStub });
// Test redisClient.get(...) to retrieve value given key using proxyquire for redisClient.
redisClientStub.redisClient.get = function (key, cb) {
cb(null, 'hello world');
};
calledBack = false;
// Test redisClient getValue async function.
redisClientProxy.getValue('some_key', function (err, value) {
assert.equal(err, null);
assert.equal('value', 'hello world');
callback = true;
});
The error when I execute the test is:
redisClientStub.redisClient.get = function (key, cb) {
^
TypeError: Cannot set property 'get' of undefined
How do I properly stub the config.redisClient.get(...) function?
I figured this out. I had to put a "stub within a stub" to stub the config.redisClient.get() function:
// Proxyquire allows unobstrusively overriding dependencies during testing.
// Override config used by RedisAccess.js.
var configStub = {
redisClient : {
createClient : function (port, address) {
// redis-mock-js used instead.
},
get : function (key, cb) {
if(key === 'test-rejected') {
cb(new Error('test-rejected'), 'rejected-promise');
}
else if(key === 'test-true') {
cb(null, true);
}
else if(key === 'test-get-valid') {
cb(null, 'valid-value');
}
else {
cb(new Error('Should not have gotten here!'), 'value');
}
},
}
};
which allowed me to construct this proxyquire:
var redisAccessProxy = proxyquire('lib/data/redis/RedisAccess.js', { 'config' : configStub });
and run this test using a proxy function for redisClient.get(...) which is called inside of RedisAccess.getValue(...):
var val = redisAccessProxy.getValue('test-get-valid');
assert.equal(val.isFulfilled(), true);
assert.equal(val.isRejected(), false);
assert.equal(val, 'valid-value');
Related
I am building a node application, and trying to neatly organize my code. I wrote a serial module that imports the serial libs and handles the connection. My intention was to write a basic module and then reuse it over and over again in different projects as needed. The only part that changes per use is how the incoming serial data is handled. For this reason I would like to pull out following handler and redefine it as per the project needs. How can I use module exports to redefine only this section of the file?
I have tried added myParser to exports, but that gives me a null and I would be out of scope.
Handler to redefine/change/overload for each new project
myParser.on('data', (data) => {
console.log(data)
//DO SOMETHING WITH DATA
});
Example usage: main.js
const serial = require('./serial');
const dataParser = require('./dataParser');
const serial = require('./serial');
//call connect with CL args
serial.connect(process.argv[2], Number(process.argv[3]))
serial.myParser.on('data',(data) => {
//Do something unique with data
if (dataParser.parse(data) == 0)
serial.send('Error');
});
Full JS Module below serial.js
const SerialPort = require('serialport');
const ReadLine = require('#serialport/parser-readline');
const _d = String.fromCharCode(13); //char EOL
let myPort = null;
let myParser = null;
function connect(port, baud) {
let portName = port || `COM1`;
let baudRate = baud || 115200;
myPort = new SerialPort(portName, {baudRate: baudRate})
myParser = myPort.pipe(new ReadLine({ delimiter: '\n'}))
//Handlers
myPort.on('open', () => {
console.log(`port ${portName} open`)
});
myParser.on('data', (data) => {
console.log(data)
});
myPort.on('close', () => {
console.log(`port ${portName} closed`)
});
myPort.on('error', (err) => {
console.error('port error: ' + err)
});
}
function getPorts() {
let portlist = [];
SerialPort.list((err, ports) => {
ports.forEach(port => {
portlist.push(port.comName)
});
})
return portlist;
}
function send(data) {
myPort.write(JSON.stringify(data) + _d, function (err) {
if (err) {
return console.log('Error on write: ', err.message);
}
console.log(`${data} sent`);
});
}
function close() {
myPort.close();
}
module.exports = {
connect, getPorts, send, close
}
The problem is that a module is used where a class or a factory would be appropriate. myParser cannot exist without connect being called, so it doesn't make sense to make it available as module property, it would be unavailable by default, and multiple connect calls would override it.
It can be a factory:
module.exports = function connect(port, baud) {
let portName = port || `COM1`;
let baudRate = baud || 115200;
let myPort = new SerialPort(portName, {baudRate: baudRate})
let myParser = myPort.pipe(new ReadLine({ delimiter: '\n'}))
//Handlers
myPort.on('open', () => {
console.log(`port ${portName} open`)
});
myParser.on('data', (data) => {
console.log(data)
});
myPort.on('close', () => {
console.log(`port ${portName} closed`)
});
myPort.on('error', (err) => {
console.error('port error: ' + err)
});
function getPorts() {
let portlist = [];
SerialPort.list((err, ports) => {
ports.forEach(port => {
portlist.push(port.comName)
});
})
return portlist;
}
function send(data) {
myPort.write(JSON.stringify(data) + _d, function (err) {
if (err) {
return console.log('Error on write: ', err.message);
}
console.log(`${data} sent`);
});
}
function close() {
myPort.close();
}
return {
myParser, getPorts, send, close
};
}
So it could be used like:
const serial = require('./serial');
const connection = serial(...);
connection.myParser.on('data',(data) => {
//Do something unique with data
if (dataParser.parse(data) == 0)
connection.send('Error');
});
I have a big app.js file and want to split the code. I took all my routes into a module called routes.js
module.exports = function(app){
app.get('/', function (req, res) {
res.redirect('/page1');
});
app.get('/page1', function (req, res) {
res.render('page1');
});
app.get('/page2/:id', function (req, res) {
res.render('page2', {
val: Number(req.params.id)
});
});
}
and in my app.js I call
const routes = require('./Server/routes')(app);
So this works fine.
I have some functions like
function loadData(id, callback) {
fs.readFile('./database.json', 'utf8', function (err, data) {
var json = JSON.parse(data);
var arr = json.arr;
var obj = arr.find(e => e.id === Number(id));
callback(obj);
});
}
and want to have them in separate files too. How can I do this? I am able to export one function but how can I export more than one?
Like
module.exports = function(){
function sayHello(){
console.log("Hello");
}
function calc(){
return 5 + 7;
}
}
require the module and call
myModule.sayHello();
var num = myModule.calc();
In your new module you can export an object and define your functions inside object
module.export = {
sayHello: function() {
console.log("Hello");
},
calc: function() {
return 5 + 7;
}
}
// usage: const mymodule = require('path/to/your/file');
also you can export a function and define your needed functions in prototype
function mymodule(someArgsIfNeeded) {
// do some initialization
}
mymodule.prototype.sayHello = function() {
console.log("Hello");
}
mymodule.prototype.calc = function() {
return 5 + 7;
}
module.export = mymodule
// usage: const mymodule = require('path/to/your/file')(someArgsIfNeeded);
I try to add an option to my app to toggle using a cache. For cache I am using Redis and made simple wrapper for that. The problem is coming when I try to overwrite redis.get method, it's simply doens't work or cannot found this.
'use strict';
const redis = require('redis');
const config = require('../config');
const REDIS_EMPTY_VALUE = 'NOT_EXIST';
const MINUTE = 60;
let client = redis.createClient({
host: config.get('REDIS_HOST'),
port: config.get('REDIS_PORT')
});
client.on("error", function (err) {
logging.error('Redis Error: ' + err);
throw new Error(err);
});
/**
* Next are custom extensions for Redis module
*/
client.emptyValue = REDIS_EMPTY_VALUE;
client.minute = MINUTE;
client.setAndExprire = function(key, value, expire) {
this.set(key, value);
this.expire(key, expire);
};
// Here is the problem
client.get = function(key, cb) {
if (config.get('disable-cache') === 'true') return cb(null, null);
return client.get(key, cb);
}
module.exports = client;
Since you override client.get, the client.get become the new function that you defined. So that, you can call to the function that come along with redis package. You can use another object (custom) to call the redis function like below:
'use strict';
const redis = require('redis');
const config = require('../config');
const REDIS_EMPTY_VALUE = 'NOT_EXIST';
const MINUTE = 60;
const definedFunctions = [
'hgetall', 'hexists', 'hmset', 'hmget', 'hkeys', 'hvals', 'hget', 'hset', 'hdel',
'mget', 'mset', 'set', 'del', 'exists', 'lpush', 'lrange',
'zrange', 'zrem', 'zadd', 'zrangebyscore', 'zrevrangebyscore',
'expire', 'incrby'
];
let client = redis.createClient({
host: config.get('REDIS_HOST'),
port: config.get('REDIS_PORT')
});
client.on("error", function (err) {
console.error('Redis Error: ' + err);
throw new Error(err);
});
/**
* Next are custom extensions for Redis module
*/
const custom = {
emptyValue: REDIS_EMPTY_VALUE,
minute: MINUTE
};
definedFunctions.map((fn) => {
custom[fn] = (...args) => {
return client[fn](args);
};
});
custom.get = function(key, cb) {
if (config.get('disable-cache') === 'true') return cb(null, null);
return client.get(key, cb);
}
module.exports = custom;
I am having issues writing unit test for the following setup as a jira.js file (in a node.js module):
var rest = require('restler'); // https://www.npmjs.com/package/restler
module.exports = function (conf) {
var exported = {};
exported.getIssue = function (issueId, done) {
...
rest.get(uri).on('complete', function(data, response) {
...
};
return exported;
};
Now, i want to write unit test for my getIssue function. 'restler' is a REST client through which i make REST calls to the JIRA API to get a JIRA issue via my code.
So to be able to test createIssue(..), I want to be able to mock the 'rest' var in my Jasmine unit tests.
How can i mock this method? Please give me some pointers so that i can go ahead. I have tried using rewire but i have failed.
This is what i have so far which does not work (ie. getIssue method turns out to be undefined):
var rewire = require("rewire");
var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();
var cfg = require("../../../config.js").Configuration;
var jiraModule = rewire("../lib/jira")(cfg);
var sinon = require("sinon");
var should = require("should");
// https://github.com/danwrong/restler
var restMock = {
init : function () {
console.log('mock initiated'+JSON.stringify(this));
},
postJson : function (url, data, options) {
console.log('[restler] POST url='+url+', data= '+JSON.stringify(data)+
'options='+JSON.stringify(options));
emitter.once('name_of_event',function(data){
console.log('EVent received!'+data);
});
emitter.emit('name_of_event', "test");
emitter.emit('name_of_event');
emitter.emit('name_of_event');
},
get : function (url, options) {
console.log('[restler] GET url='+url+'options='+JSON.stringify(options));
},
del : function (url, options) {
console.log('[restler] DELETE url='+url+'options='+JSON.stringify(options));
},
putJson : function (url, data, options) {
console.log('[restler] PUT url='+url+', data= '+JSON.stringify(data)+
'options='+JSON.stringify(options));
}
};
var cfgMock = {
"test" : "testing"
};
jiraModule.__set__("rest", restMock);
jiraModule.__set__("cfg", cfgMock);
console.log('mod='+JSON.stringify(jiraModule.__get__("rest")));
describe("A suite", function() {
it("contains spec with an expectation", function() {
restMock.init();
restMock.postJson(null, null, null);
console.log(cfg.jira);
// the following method turns out to be undefined but when i console.log out the jiraModule, i see the entire code outputted from that file
jiraModule.getIssue("SRMAPP-130", function (err, result) {
console.log('data= '+JSON.stringify(result));
});
expect(true).toBe(true);
});
});
If someone can guide me on how to mock the 'rest' require dependency & unit test this method that will be very helpful.
Also, how should i mock the 'conf' being passed to module.exports?
thanks
You could use proxyquire or mockery to stub/mock the dependencies.
In the below example I have used proxyquire. Hope it helps.
/* ./src/index.js */
var rest = require('restler');
module.exports = function (conf) {
var exported = {};
exported.getIssue = function (issueId, done) {
var uri = '';
var reqObj = '';
var service = {
auth : ''
};
rest.postJson(uri, reqObj, service.auth).on('complete', function(data, response) {
done(data, response);
});
};
return exported;
};
/* ./test/index.js */
var proxyquire = require('proxyquire');
var assert = require('chai').assert;
var restlerStub = {
postJson: function() {
return {
on: function(event, callback) {
callback('data', 'response');
}
}
}
}
var index = proxyquire('../src/index', {'restler': restlerStub})();
describe('index', function() {
it('should return the desired issue', function(done) {
var issue = index.getIssue('issueId', function(data, response) {
assert.equal(data, 'data');
assert.equal(response, 'response');
done();
})
});
});
/* ./package.json */
{
"scripts": {
"test": "mocha"
},
"dependencies": {
"restler": "^3.4.0"
},
"devDependencies": {
"chai": "^3.4.1",
"mocha": "^2.3.4",
"proxyquire": "^1.7.3"
}
}
I'm experimenting with Linq2IndexedDB (v. 1.0.21) via unit tests (via Mocha), but I can't even make a simple insert work. What happens (when running under Google Chrome) is an internal exception is thrown on line 1535 of Linq2IndexedDB.js:
Uncaught TypeError: Cannot read property 'version' of undefined
My unit test code looks as follows; there's basically one test, "it can add objects":
"use strict";
define(["db", "linq2indexeddb", "chai", "underscore", "stacktrace"], function (db, linq2indexeddb, chai, _,
printStacktrace) {
var should = chai.should();
describe("db", function () {
var _db;
function fail(done, reason, err) {
if (typeof reason === "string") {
reason = new Error(reason);
}
if (!reason) {
console.log(typeof done, typeof reason);
reason = new Error("There's been an error, but no reason was supplied!");
var st = printStacktrace({e: reason});
console.log(st);
}
if (typeof done !== "function") {
throw new Error("Was not supplied a function for 'done'!");
}
done(reason);
}
// Bind done as the first argument to the fail function
function bindFail(done, reason) {
if (typeof done !== "function") {
throw new Error("done must be a function");
}
return _.partial(fail, done, reason);
}
beforeEach(function (done) {
_db = linq2indexeddb("test", null, true);
_db.deleteDatabase()
.done(function () {
_db.initialize()
.done(done)
.fail(bindFail(done, "Initializing database failed"));
})
.fail(bindFail(done, "Deleting database failed"));
});
it("can add objects", function (done) {
console.log("Starting test");
var refObj = {"key": "value"};
_db.linq.from("store").insert(refObj, "Key")
.done(function () {
console.log("Added object successfully");
done();
})
.fail(bindFail(done, "Inserting object failed"));
});
});
});
Am I doing something wrong here, or is there a bug in Linq2IndexedDB (or both)?
I've put up a corresponding test project on Github, complete with a Karma configuration, so you can run the included tests easily. The Karma configuration assumes you have Chrome installed.
I found a couple of issues:
I got the Linq2IndexedDB worker location wrong, it should be: '/base/lib/Linq2IndexedDB.js'
Inserting with the object with an out-of-line key doesn't work.
I eventually got insertion working on IE 10 and Chrome, although I'm still struggling with PhantomJS. To get it working under Chrome, I had to specify my schema explicitly, I suspect this is due to a bug in Linq2IndexedDB. My working solution is as follows:
Test:
"use strict";
define(["db", "linq2indexeddb", "chai", "underscore", "stacktrace"], function (db, linq2indexeddb, chai, _,
printStacktrace) {
var should = chai.should();
describe("db", function () {
var _db;
function fail(done, reason, err) {
console.log("err:", err);
if (typeof reason === "string") {
reason = new Error(reason);
}
if (!reason) {
console.log(typeof done, typeof reason);
reason = new Error("There's been an error, but no reason was supplied!");
var st = printStacktrace({e: reason});
console.log(st);
}
if (typeof done !== "function") {
throw new Error("Was not supplied a function for 'done'!");
}
done(reason);
}
// Bind done as the first argument to the fail function
function bindFail(done, reason) {
if (typeof done !== "function") {
throw new Error("done must be a function");
}
return _.partial(fail, done, reason);
}
beforeEach(function (done) {
// Linq2IndexedDB's web worker needs this URL
linq2indexeddb.prototype.utilities.linq2indexedDBWorkerFileLocation = '/base/lib/Linq2IndexedDB.js'
_db = new db.Database("test");
console.log("Deleting database");
_db.deleteDatabase()
.done(function () {
console.log("Initializing database");
_db.initialize()
.done(done)
.fail(bindFail(done, "Initializing database failed"));
})
.fail(bindFail(done, "Deleting database failed"));
});
it("can add objects", function (done) {
console.log("Starting test");
var refObj = {"key": "value"};
_db.insert(refObj)
.done(function () {
done();
})
.fail(bindFail(done, "Database insertion failed"));
});
});
});
Implementation:
define("db", ["linq2indexeddb"], function (linq2indexeddb) {
function getDatabaseConfiguration() {
var dbConfig = {
version: 1
};
// NOTE: definition is an array of schemas, keyed by version;
// this allows linq2indexedDb to do auto-schema-migrations, based upon the current dbConfig.version
dbConfig.definition = [{
version: 1,
objectStores: [
{ name: "store", objectStoreOptions: { keyPath: 'key' } },
],
defaultData: []
},
];
return dbConfig;
}
var module = {
Database: function (name) {
var self = this;
self._db = linq2indexeddb(name, getDatabaseConfiguration());
self.deleteDatabase = function () {
return self._db.deleteDatabase();
};
self.initialize = function () {
return self._db.initialize();
};
self.insert = function (data) {
return self._db.linq.from("store").insert(data);
};
}
};
return module;
});
EDIT:
The reason it wasn't working under PhantomJS was that I'd enabled debug logging in Linq2IndexedDB, for some reason this would seemingly clog up Karma's pipes at some point. After turning off debug logging, all configurations work.