How to stub a dynamic object method using Sinon.js? - javascript

I have following module.
var Sendcloud = require('sendcloud');
var sc = new Sendcloud("key1","key2","key3");
var service = {};
service.restorePassword = function (params, cb) {
if (!params.to || !params.name || !params.token) {
throw "Miss params"
}
var defaultTemplate = adminBaseUrl + "reset/token/" + params.token;
var subject = params.subject || "Letter";
var template = params.template || defaultTemplate;
// Send email
sc.send(params.to, subject, template).then(function (info) {
console.log(info)
if (info.message === "success") {
return cb(null, "success");
} else {
return cb("failure", null);
}
});
};
module.exports = service;
I experience problems stubbing sc.send method. How to correctly cover this point using sinon.js? Or maybe I need to replace sendcloud module?

You need to use proxyquire module or rewire module.
Here an example of using proxyquire
var proxyquire = require('proxyquire');
var sinon = require('sinon');
var Sendcloud = require('sendcloud');
require('sinon-as-promised');
describe('service', function() {
var service;
var sc;
beforeEach(function() {
delete require.cache['sendcloud'];
sc = sinon.createStubInstance(Sendcloud);
service = proxyquire('./service', {
'sendcloud': sinon.stub().returns(sc)
});
});
it('#restorePassword', function(done) {
sc.send.resolves({});
var obj = {
to: 'to',
name: 'name',
token: 'token'
};
service.restorePassword(obj, function() {
console.log(sc.send.args[0]);
done();
});
});
});

Related

Building a slack bot in node.js, throwing an error I can't make sense of. Has anyone seen this before?

I am trying to build a slack bot, and I have come across an error I can't make sense of.
This is the error:
/Users/maecapozzi/Desktop/maebot/node_modules/vow/lib/vow.js:104
throw e;
^
Error: [Slack Bot Error] undefined
at assert (/Users/maecapozzi/Desktop/maebot/node_modules/slackbots/libs/utils.js:15:15)
at /Users/maecapozzi/Desktop/maebot/node_modules/slackbots/index.js:42:9
at Array.<anonymous> (/Users/maecapozzi/Desktop/maebot/node_modules/vow/lib/vow.js:712:56)
at Immediate.callFns [as _onImmediate] (/Users/maecapozzi/Desktop/maebot/node_modules/vow/lib/vow.js:23:35)
at tryOnImmediate (timers.js:534:15)
at processImmediate [as _immediateCallback] (timers.js:514:5)
I'll share my code with you as well, since that should help to make more sense of what has happened.
bot.js :
'use strict';
var MaeBot = require('../lib/maebot');
var token = process.env.BOT_API_KEY;
var dbPath = process.env.BOT_DB_PATH;
var name = process.env.BOT_NAME;
var maebot = new MaeBot({
token: token,
dbPath: dbPath,
name: name
});
maebot.run();
database.js:
var pg = require('pg');
var connectionString = process.env.DATABASE_URL || 'postgres://localhost:5432/maebot';
var client = new pg.Client(connectionString);
client.connect();
var query = client.query('CREATE TABLE dacts(id SERIAL PRIMARY KEY, text VARCHAR(40) not null)');
query.on('end', function() { client.end(); });
maebot.js:
'use strict';
var util = require('util');
var path = require('path');
var fs = require('fs');
var PostGres = require('postgresql');
var Bot = require('slackbots');
var MaeBot = function Constructor(settings) {
this.settings = settings;
this.settings.name = this.settings.name || 'maebot';
this.dbPath = settings.dbPath || path.resolve(process.cwd(), 'data', 'database.js');
this.user = null;
this.db = null;
};
MaeBot.prototype.run = function () {
MaeBot.super_.call(this, this.settings);
this.on('start', this._onStart);
this.on('message', this._onMessage);
};
MaeBot.prototype._onStart = function () {
this._loadBotUser();
this._connectDB();
};
MaeBot.prototype._loadBotUser = function () {
var self = this;
this.user = this.users.filter (function (user) {
return user.name === self.name;
})[0];
};
MaeBot.prototype._connectDB = function () {
if (!fs.existsSync(this.dbPath)) {
console.error('Database path ' + '"' + this.dbPath + '" does not exists or it\'s not readable."')
process.exit(1);
}
this.db = new PostGres.Database(this.dbPath);
};
MaeBot.prototype._welcomeMessage = function () {
this.postMessageToChannel(this.channels[0].name, 'Hi! Maebot here.' +
'\n I can tell you about my creator, Mae. Just say `Hi, maebot` or `' + this.name + '` to invoke me!',
{as_user: true});
};
MaeBot.prototype._onMessage = function (message) {
if (this._isChatMessage(message) &&
this._isChannelConversation(message) &&
!this._isFromMaeBot(message) &&
this._isMentioningMaeBot(message)
) {
this._replyWithRandomFact(message);
}
};
MaeBot.prototype._isChatMessage = function (message) {
return message.type === 'message' && Boolean(message.text);
};
MaeBot.prototype._isChannelConversation = function (message) {
return typeof message.channel === 'string' &&
message.channel[0] === 'C';
};
MaeBot.prototype._isFromMaeBot = function (message) {
return message.user === this.user.id;
};
MaeBot.prototype._isMentioningMaeBot = function (message) {
return message.text.toLowerCase().indexOf('maebot') > -1 ||
message.text.toLowerCase().indexOf(this.name) > -1;
};
MaeBot.prototype._replyWithRandomFact = function (originalMessage) {
var self = this;
self.db.get('SELECT id, fact FROM facts ORDER BY used ASC, RANDOM() LIMIT 1', function (err, record) {
if (err) {
return console.error('DATABASE ERROR:', err);
}
var channel = self._getChannelById(originalMessage.channel);
self.postMessageToChannel(channel.name, record.fact, {as_user: true});
self.db.run('UPDATE facts SET used = used + 1 WHERE id = ?', record.id);
});
};
MaeBot.prototype._getChannelById = function (channelId) {
return this.channels.filter(function (item) {
return item.id === channelId;
})[0];
};
util.inherits(MaeBot, Bot);
module.exports = MaeBot;
Your bot user and/or token is likely incorrect. Try re-creating your bot and ensure that you're using a valid token for your app.

mocking MongoDB with proxyquire

I'd like to mock MongoDB dependency with proxyquire
by doing this in my test:
var proxyquire = require('proxyquire');
var controller = path.resolve('.path/to/controller/file.js');
in the before each statement:
mocked_mongoose = {
isMocked: true,
model: function(name, schema, collection, skipInit) {
return {
find: function(conditions, projection, options, callback) {
console.log('callback find');
return callback();
},
save: function(options, fn) {
console.log('callback save');
return callback();
},
findOne: function(conditions, projection, options, callback) {
console.log('callback find one');
var model = mongoose.model(name);
var fakeModel = fakery.fake(model);
return callback(null, fakemodel);
}
}
}
};
proxyquire(controller, {
'mongoose': mocked_mongoose
});
and when I go to the controller and do
console.log(mongoose.isMocked) I got undefined and if I print mongoose.model.toString() seems like the mongoose methods aren't overridden.
I followed up this article and tried to implement the same logic, but I'm not getting the same results.
any help will be appreciated,
thanks!
I ended up by changing the way I was defining my mocked mongoose object, to match exactly the scenarios I wanted to test in mt case:
first model instantiation
var Model = mongoose.model('SchemaDef');
var other = Model({
_id:'someId'
name'Some other fields'
});
model search:
Model.findOne(query, callback);
this the version how it works:
'use strict';
var factory = require('factory-girl');
var mongoose = require('mongoose');
function viewModel(name){
function constructor(obj){
if(obj){
obj.find = constructor.find;
obj.save = constructor.save;
obj.findOne = constructor.findOne;
}
return obj;
};
constructor.isMocked = true;
constructor.find = function(query, callback){
factory.build(name, function(err, mockedModel){
return callback(null,[mockedModel]);
});
};
constructor.save = function(callback){
factory.build(name, function(err, mockedModel){
return callback(null,[mockedModel]);
});
};
constructor.findOne=function(query,callback){
factory.build(name, function(err, mockedModel){
return callback(null,mockedModel);
});
};
return constructor;
};
module.exports = {
model:function(name){
factory.define(name, mongoose.model(name));
return viewModel(name);
}
};

NodeJS module export

I have a simple http client module(api.js) which is returning a promise as in the followings:
exports.endpoint = '';
exports.GET = function(args){
args.method = 'GET';
args.uri = this.endpoint + args.uri;
return asyncApiCall(args);
};
exports.POST = function(args){
args.method = 'POST';
args.uri = this.endpoint + args.uri;
return asyncApiCall(args);
};
exports.PUT = function(args){
args.method = 'PUT';
args.uri = this.endpoint + args.uri;
return asyncApiCall(args);
};
exports.DELETE= function(args){
args.method = 'DELETE';
args.uri = this.endpoint + args.uri;
return asyncApiCall(args);
};
var asyncApiCall = function(args){
var rp = require('request-promise');
var options = {
method: args.method,
uri: args.uri,
body : args.body,
json: args.json
}
return rp(options);
};
and I use the module like this:
var api = require('./api.js');
var args = {
uri : '/posts'
}
api.endpoint = 'http://localhost:3000';
api.GET(args)
.then(function(res){
console.log(res);
}, function(err){
console.log(err);
});
Now, I want to improve the module as much as possible. Is there any way to not repeat the export.functionName? I found the module.exports in NodeJS, but I am not sure how to use it in this case. How can I set the endpoint variable once in asyncApiCall function instead of all the other functions that return asyncApiCall?
Just another style:
var rp = require('request-promise'); // Put it here so you don't have to require 1 module so many times.
var asyncApiCall = function(args) {
var options = {
method: args.method,
uri: args.uri,
body : args.body,
json: args.json
};
return rp(options);
};
// Let's hack it.
var endpoint = '';
var apis = {};
['GET', 'POST', 'PUT', 'DELETE'].forEach(function(method) {
apis[method] = function(args) {
args.method = method;
args.uri = endpoint + args.uri;
return asyncApiCall(args);
}
});
module.exports = apis;
module.exports.endpoint = '';
A lot of people chose to put their export methods on a new object and export via module.exports, e.g.
var myExports = {
get: function () {},
post: function () {}
}
module.exports = myExports;
As for module.exports vs exports
It looks like it may be appropriate to set up a full constructor with your methods tied to it, like so:
var requests = function (endpoint) {
this.endpoint = endpoint;
}
requests.prototype.GET = function (args) {
args.method = 'GET';
args.uri = this.endpoint + args.uri;
return asyncApiCall(args);
}
// And so on
module.exports = requests;
And then call it like so:
var api = require('./api.js');
var endpoint = new api("http://localhost:3000");
endpoint.GET()
Wrap this in a class and export a new instance of it
function Module() {
}
Module.prototype.GET = function () {}
module.export = new Module()
// or
module.export = Module
// to call the constructor for your endpoint variable.

Rewrite event emitter with Reactive programming(Semaphore example)

I'm use event emitters as synchronization primitives. For example I have one class which asks semaphore like structure in Redis. If semaphore is set it emits an event. The code is listed bellow:
var redis = require("redis"),
async = require('async'),
client = redis.createClient(),
assert = require('assert'),
EventEmitter = require('events').EventEmitter;
util = require('util');
var isMaster = false,
SEMAPHORE_ADDRESS = 'semaphore';
var SemaphoreAsker = function() {
var self = this;
var lifeCycle = function (next) {
client.set([SEMAPHORE_ADDRESS, true, 'NX', 'EX', 5], function(err, val) {
console.log('client');
if(err !== null) { throw err; }
if(val === 'OK') {
self.emit('crown');
} else {
console.log('still a minion');
}
});
};
async.forever(
function(next) {
setTimeout(
lifeCycle.bind(null, next),
1000
);
}
);
};
util.inherits(SemaphoreAsker, EventEmitter);
(new SemaphoreAsker()).on('crown', function() {
console.log('I`m master');
});
It works but looks a little bit heavy. Is it possible to rewrite the example with BaconJS(RxJS/whateverRPlibrary)?
The following should work in RXJS:
var callback = Rx.Observable.fromNodeCallback(client.set, client);
var source = Rx.Observable.interval(1000)
.selectMany(function() {
return callback([SEMAPHORE_ADDRESS, true, 'NX', 'EX', 5]);
})
.filter(function(x) { return x === 'OK'; })
.take(1);
source.subscribe(function(x) {
console.log("I am master");
});
If you are willing to additionally include the rx-node module you can also keep your event emitter structure by using
var emitter = RxNode.toEventEmitter(source, 'crown');
emitter.on('crown', function(){});
emitter.on('error', function(){});
emitter.on('end', function(){});
I used the basic Bacon.fromBinder to create a custom stream for this. Without a working example this is a bit of guesswork, but hopefully this helps you.
var redis = require("redis"),
client = redis.createClient(),
assert = require('assert'),
Bacon = require('bacon');
var SEMAPHORE_ADDRESS = 'semaphore';
var SemaphoreAsker = function() {
return Bacon.fromBinder(function (sink) {
var intervalId = setInterval(pollRedis, 1000)
return function unsubscribe() {
clearInterval(intervalId)
}
function pollRedis() {
client.set([SEMAPHORE_ADDRESS, true, 'NX', 'EX', 5], function(err, val) {
if(err !== null) { sink(new Bacon.Error(err)) }
else if(val === 'OK') { sink(new Bacon.Next('crown'))
else { assert.fail(); }
}
}
})
}
SemaphoreAsker().take(1).onValue(function() {
console.log("I am master")
})
The #paulpdanies answer but rewritten with Bacon:
var source = Bacon.interval(1000).flatMap(function() {
return Bacon.fromNodeCallback(
client, 'set', [SEMAPHORE_ADDRESS, true, 'NX', 'EX', 1]
);
})
.filter(function(x) { return x === 'OK'; })
.take(1);
source.onValue(function(x) {
console.log(x);
});

function as parameter in node.js module

in this code: if i try to pass this.handler as parameter to server.createServer() , then i get no response (the page keeps loading in browser). but if i use server.createServer(function(req, res) { //same code here as in handler() }) , then it works. what am i doing wrong?
var Con = module.exports = function() {
process.EventEmitter.call(this);
}
var createServer = module.exports.createServer = function(options) {
console.log('start');
this.port = options.port || 9122;
this.secure = options.secure || false;
if(this.secure === true)
if(!options.key || !options.certificate)
this.secure = false;
else {
this.key = options.key;
this.certificate = options.certificate;
}
if(this.secure) {
this.server = require('https');
var fs = require('fs');
var opt = {
key: fs.readFileSync('privatekey.pem'),
cert: fs.readFileSync('certificate.pem')
};
this.server.createServer(opt, this.handler).listen(this.port);
} else {
this.server = require('http');
this.server.createServer(this.handler).listen(this.port);
}
}
Con.prototype.handler = function(req, res) {
console.log('request');
res.writeHead(200);
res.write(req.url);
res.end();
}
var Con = function() {
process.EventEmitter.call(this);
}
That's your constuctor
module.exports = new Con();
That's your instance
var createServer = module.exports.createServer = function(options) {
console.log('start');
this.port = options.port || 9122;
this.secure = options.secure || false;
if(this.secure === true)
if(!options.key || !options.certificate)
this.secure = false;
else {
this.key = options.key;
this.certificate = options.certificate;
}
if(this.secure) {
this.server = require('https');
var fs = require('fs');
var opt = {
key: fs.readFileSync('privatekey.pem'),
cert: fs.readFileSync('certificate.pem')
};
this.server.createServer(opt, this.handler).listen(this.port);
} else {
this.server = require('http');
this.server.createServer(this.handler).listen(this.port);
}
}
.createServer is now a method on the instance rather then the constructor.
Since it's on the instance it also has access to the .handler method defined on the instance through the prototype.

Categories

Resources