Utilising Nodejs Promises (Bluebird) during module loading and configuration - node.js

Morning All
I've been scouring S.O. for the past couple of days trying to find an answer I understood/was applicable to my situation, and have now admitted defeat, primarily due to my lack of comprehension around Promises (promise-n00b). So I'm basically putting out a plea for some help with my example posted below. I think it's fairly explanatory what I'm doing but in case it isn't, I'm trying to:
Apply some synchronous config to the server
Load a Logger module and instantiate a new of instance of it as logger, and then run some necessary checks (e.g. do we have a log file?) before returning an indicator to say that it loaded successfully (either a boolean or the logger object itself)
Then pass that logger to the utils (they need it to log)
Finally call the callback passed in by the scripts run by npm start and npm test
Obviously there's a lot more going on in the real world but I'd tried to distil the code down to the bit I don't get, namely the Promise chain.
Finally, as a long term user of callbacks who has never struggled to comprehend them (brain must work differently perhaps) do any of you dudes have any pearls of wisdom likely to cause a lightbulb moment?
Many thanks in advance and yes I know this code as it stands doesn't/won't work ;-)
Code as follows:
server.js
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs-extra'));
var boss = {
start: function(callback) {
var p = new Promise.try(function() {
console.log('start');
})
.then(boss.applyConfig)
.then(boss.startLogger)
.then(function(lggr) {
console.log('logger setup complete: ' + lggr);
boss.logger = lggr;
return lggr;
})
.then(boss.loadUtils)
.finally(function() {
if (callback) callback();
})
.catch(function(err) {
console.log.call(null, '\033[31m' + err.stack + ' \033[39m');
});
},
applyConfig: function() {
console.log('applying config');
return 'config';
},
startLogger: function() {
console.log('starting logger');
var Logger = require('./Logger')(fs, Promise);
var logger = new Logger();
return new Promise(function(resolve, reject) {
var result = logger.init();
if (result) {
resolve(logger);
}
else {
reject(logger);
}
});
},
loadUtils: function(logger) {
console.log('loading utils with ' + logger);
boss.logger.log('loading utils with ' + logger);
return 'utils';
}
};
boss.start(function(){
console.log('done');
});
Logger.js
module.exports = function(fs, Promise) {
var Logger = function(options) {
this.filePath = (options && options.filePath) ? options.filePath : __dirname + '/../log/';
this.filename = (options && options.filename) ? options.filename : 'all.log';
};
Logger.prototype.init = function() {
var logger = this;
console.log('logger.init');
return new Promise(function(resolve) {
new Promise.resolve(logger.checkForLogFile()).then(function(exs) {
console.log('exs', exs);
return exs;
});
})
};
Logger.prototype.log = function(msg) {
// requires that the log file exists and that we have a stream to it (create stream function removed)
console.log.call(null, '\033[36mLogging: ' + msg + ' \033[39m');
};
Logger.prototype.checkForLogFile = function() {
var logger = this;
fs.existsAsync(logger.filePath + logger.filename).then(function (exists) {
console.log('exists', exists);
return exists;
});
};
return Logger;
};

First up - thanks Benjamin :) And secondly, how amazingly surprising that all this would now work within 30 mins of posting on S.O. after a couple of days prior effort ;-)
So yes - golden takeaway nugget is KISS!
Following code now works correctly and logs in the correct order as follows:
start
applying config
starting logger
logger.init
statObj { dev: 2067,
mode: 33204,
...
ctime: Thu Nov 27 2014 11:40:34 GMT+0000 (GMT) }
exists true
logger setup complete: [object Object]
loading utils with [object Object]
Logging: loading utils with [object Object]
done
server.js
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs-extra'));
var boss = {
start: function(callback) {
var p = new Promise.try(function() {
console.log('start');
})
.then(boss.applyConfig)
.then(boss.startLogger)
.then(function(lggr) {
console.log('logger setup complete: ' + lggr);
boss.logger = lggr;
return lggr;
})
.then(boss.loadUtils)
.finally(function() {
if (callback) callback();
})
.catch(function(err) {
console.log.call(null, '\033[31m' + err.stack + ' \033[39m');
});
},
applyConfig: function() {
console.log('applying config');
return 'config';
},
startLogger: function() {
console.log('starting logger');
var Logger = require('./Logger')(fs, Promise);
var logger = new Logger();
return logger.init();
},
loadUtils: function(logger) {
console.log('loading utils with ' + logger);
boss.logger.log('loading utils with ' + logger);
return 'utils';
}
};
boss.start(function(){
console.log('done');
});
Logger.js
module.exports = function(fs, Promise) {
var Logger = function(options) {
this.filePath = (options && options.filePath) ? options.filePath : __dirname + '/../log/';
this.filename = (options && options.filename) ? options.filename : 'all.log';
};
Logger.prototype.init = function() {
var logger = this;
console.log('logger.init');
return logger.checkForLogFile().then(function(statObj) {
var exists = (typeof statObj === 'object');
console.log('exists', exists);
return logger;
});
};
Logger.prototype.log = function(msg) {
// requires that the log file exists and that we have a stream to it (create stream function removed)
console.log.call(null, '\033[36mLogging: ' + msg + ' \033[39m');
};
Logger.prototype.checkForLogFile = function() {
var logger = this;
return fs.statAsync(logger.filePath + logger.filename).then(function (statObj) {
console.log('statObj', statObj);
return statObj;
});
};
return Logger;
};

Related

Protractor test case freezes when run on bamboo

When I run my test on Visual studio my test runs successfully. But when I try to run it on bamboo my browser freezes and my test fails with the jasmine timeout exception.
Please find my protractor conf file :
var HtmlScreenshotReporter = require('protractor-jasmine2-screenshot-reporter');
var reporters = require('jasmine-reporters');
var co = require('co');
// Example usage:
// protractor --env=local
// protractor --env=local --specs=test/road/road-missing.js
var reporter = new HtmlScreenshotReporter({
dest: 'target/screenshots',
filename: 'Test_Report.html',
fixedScreenshotName: true
});
var junitReporter = new reporters.JUnitXmlReporter({
savePath: 'target/screenshots',
consolidateAll: false
});
var customReporter = {
specStarted: function(result) {
console.log('\n---------------- Spec started: ' + result.fullName + ' ----------------');
}
}
var FUNCTION_CONFIG = {
beforeLaunch: function () {
return new Promise(function (resolve) {
reporter.beforeLaunch(resolve);
});
},
onPrepare: function () {
// cleanup of a failed test, generating an alert: "You have unsent changes."
// This will log the error:
// UnexpectedAlertOpenError: unexpected alert open: {Alert text : You have unsent changes.}
afterEach(co.wrap(function*() {
browser.ignoreSynchronization = true;
// force leaving page by open page about:blank
yield browser.get("about:blank");
try {
// if no alert is shown an exception is thrown. This is the case if a test finished successful
var alert = yield browser.switchTo().alert();
yield alert.accept();
} catch (e) {
// case: no alert was shown. accessing alert will trhow an exception, which can be ignored
}
}));
browser.manage().window().setSize(480, 900);
jasmine.getEnv().addReporter(reporter);
var jasmineReporters = require('jasmine-reporters');
jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({
consolidateAll: true,
savePath: 'target/screenshots',
filePrefix: 'xmloutput'
}));
jasmine.getEnv().addReporter(customReporter);
},
afterLaunch: function (exitCode) {
return new Promise(function (resolve) {
reporter.afterLaunch(resolve.bind(this, exitCode));
});
}
};
function extend(obj, source) {
if (source) {
for (var prop in source) {
if (typeof obj[prop] === "object" && !Array.isArray(obj[prop]))
extend(obj[prop], source[prop]);
else
obj[prop] = source[prop];
}
}
return obj;
};
function getArgv(parameter) {
var argv = process.argv;
var param = parameter + "=";
for (var i = 0; i < argv.length; i++) {
if (argv[i].startsWith(parameter))
return argv[i].substring(param.length);
}
return undefined;
}
var DEFAULT_CONFIG = require('./env/default.js');
var env = getArgv('--env') || 'local';
var ENV_CONFIG = require('./env/' + env + '.js');
var conf = extend(FUNCTION_CONFIG, DEFAULT_CONFIG);
exports.config = extend(
conf,
ENV_CONFIG
);
Also this is one of the test case which fails on browser :
var UI = require('./../ui.js');
var co = require('co');
var ui = new UI();
describe("MapFeedback: address-infowrong", function () {
ui.setSmallScreenSize();
// ui.testLogger(100);
it("test", co.wrap(function* () {
yield browser.get(ui.createAddressStartLink());
yield ui.ADDRESS_PROBLEM.click();
var version = yield browser.executeScript('return mapfeedbackVersion');
console.log("Map feedback Version:" + version);
expect(browser.getCurrentUrl()).toContain("report_address");
yield ui.ADDRESS_INFO_WRONG.click();
expect(browser.getCurrentUrl()).toContain("select_address_edit/address_edit");
yield ui.zoomIn(18.5);
var elmOK = element(By.css('button[ng-click="doneSelectObject(selectedObject)"]'));
yield ui.waitFor(protractor.ExpectedConditions.elementToBeClickable(elmOK));
yield elmOK.click();
expect(browser.getCurrentUrl()).toContain("address_edit");
yield element(by.xpath("/html/body/div[1]/div/div/div[2]/div[2]/div[1]/div[1]/input")).click();
yield element(by.xpath("//*[#id=\"input_addressNumber\"]")).sendKeys("TEST");
yield element(by.xpath(" /html/body/div[1]/div/div/div[2]/div[2]/div[4]/div[1]/input")).click();
yield element(by.xpath("//*[#id=\"input_address_other\"]")).sendKeys("TEST");
yield ui.SUBMIT.click();
expect(browser.getCurrentUrl()).toContain("submit");
yield ui.waitSubmit();
var reportId = yield browser.executeScript('return mapFeedBack.reportId');
console.log("address-infowrong id: " + reportId);
expect(yield element(By.css('div[ng-show="mapFeedBack.submitState==\'success\'"]')).isDisplayed()).toBeTruthy();
var req = yield ui.getMapFeedbackData(-reportId);
var body = JSON.parse(req.body);
console.log(body);
expect(body.properties.error).toEqual(21);
expect(body.properties.details).toEqual("Other address issue: TEST");
}));
});
Note : I am using a selenium grid for my test automation.
// --- config: default
module.exports = {
params: {
baseUrl: "NOT-SET",
urlParams: "zoomLevel=15",
urlParamCoords: "55.67653,12.56040", // Copanhagen Location
urlAddressParamCoords: "40.75170,-73.99420", //New York Location,
rest: {
baseUrl: "NOT-SET",
header: {
// 'Content-Type': 'application/vnd.here.layerObjectList+json; charset=UTF-8',
'Auth-Identifier': 'Y-7Wnc2lNk3fMI0n3-rZ',
'Auth-Secret': 'mEv_DZ1JKDDYzESUAp9KyQ',
'Auth-Service-Id': 'here_app',
'User-Agent': 'mapfeedback-test',
'Accept': 'application/vnd.here.layerObject+json',
'Group-Id': 'FGx1AWaAzKOo0imNkLmf'
}
}
},
seleniumAddress: "http://selenium.community.nw.ops.here.com:4444/wd/hub",
// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome',
maxInstances: 1
},
// Framework to use. Jasmine is recommended.
framework: 'jasmine',
// Spec patterns are relative to the current working directly when protractor is called.
specs: [
// Note: group & sort tests by feature
'./test/address/address-remove.js',
'./test/address/address-missing.js',
'./test/address/address-infowrong.js',
'./test/border/border-linemissing.js',
'./test/border/border-linedrawnwrong.js',
'./test/other/other-cartoincorrect.js',
'./test/other/other-cartomissing.js',
'./test/other/other-cartoremove.js',
'./test/place/place-closed.js',
'./test/place/place-offensivecontent.js',
'./test/place/place-violateprivacy.js',
'./test/place/place-Inappropriatebehaviour.js',
'./test/place/place-otherviolation.js',
'./test/place/place-copyright.js',
'./test/place/place-otherissue.js',
'./test/place/place-missing.js',
'./test/place/place-infowrong.js',
'./test/road/road-missing.js',
'./test/road/road-closed.js',
'./test/road/road-infowrong.js',
'./test/satellite/satellite-conflictimage.js',
'./test/satellite/satellite-obscuredimage.js',
'./test/satellite/satellite-oldimage.js',
'./test/satellite/satellite-unclearimage.js',
],
// Options to be passed to Jasmine.
jasmineNodeOpts: {
defaultTimeoutInterval: 50000
}
};
Please help ??

Variable precedence (global in node js?)

"use strict";
var Tabletop = require("tabletop");
var base64 = require('base-64');
Tabletop.init( { key: 'xxxxxg46hgfjd',
callback: showInfo,
simpleSheet: true } )
function showInfo(data, tabletop) {
console.log(data);
console.log(base64.encode(data));
}
var vGlobals = {
dataString: base64.encode(data)
};
module.exports = vGlobals;
How can I access the data variable from showInfo, to use in vGlobals? It says that it hasn't been defined.
Your approach is wrong, you can't do it this way because TableTop call your callback asynchronously.
My suggestion (a quick one) :
var dataString = null;
module.exports = function(cb) {
if (dataString == null)
Tabletop.init({
key: 'xxxxxg46hgfjd',
callback: function(data, tabletop) {
dataString = base64.encode(data);
cb(dataString);
},
simpleSheet: true
});
else cb(dataString);
};
And to get the data :
var dataManager = require('./myfile');
dataManager(function(dataString) {
//here is your data do what you want with it
})
You should look/learn more about node/javascript and asynchronous/event-driven programing.

Why does this Bottleneck-ed app stall?

I have the app below and it stalls (code below). And I have no idea why. I suspect I might be using the Bottleneck module the wrong way.
Disclaimer: I am trying to learn programming and NodeJS myself using this project. Please help.
Intro
The point of the app is to fetch data missing from documents in a DB by requesting a webpage and parsing it jQuery-style. Then saving the returned data to new keys in the document. The database consists of ~92 000 documents. The app uses the bottleneck, cheerio and request modules. I run the app on OS X.
The problem
If I set a limit to the number of requests, such as
var limiter = new bottleneck(5, 0);
The app stalls after the first batch (5 in this case). But why? I suspect something might be wrong with Bottleneck and how it expects my program to work. Something to do with callbacks per Bottleneck "Gotchas" maybe?
If I set no limit, the app kind-of-works. It fetches webpages and writes to the DB. However with a lot of errors due to resources being overloaded and thus slowly. This is how I tell bottleneck not to limit:
var limiter = new bottleneck(0, 0);
These are the kind of errors I get:
{ [Error: getaddrinfo ENOTFOUND www.vestnikverejnychzakazek.cz www.vestnikverejnychzakazek.cz:443]
code: 'ENOTFOUND',
errno: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'www.vestnikverejnychzakazek.cz',
host: 'www.vestnikverejnychzakazek.cz',
port: 443 }
{ [Error: connect EMFILE 65.52.146.11:443 - Local (undefined:undefined)]
code: 'EMFILE',
errno: 'EMFILE',
syscall: 'connect',
address: '65.52.146.11',
port: 443 }
App code
'use strict';
var express = require('express');
var router = express.Router();
var assert = require('assert');
var mongo = require('mongoskin');
var path = require('path');
var ObjectID = require('mongodb').ObjectID;
var db = mongo.db("mongodb://localhost:27017/zak", {
native_parser: true
});
var database = db.collection("zakazky");
var cheerio = require("cheerio");
var request = require("request");
var fs = require("fs");
var toJs = (path.join(__dirname, '../public/javascripts', 'jquery.min.js'));
var jquery = fs.readFileSync(toJs).toString();
var bottleneck = require("bottleneck");
var limiter = new bottleneck(5, 0);
/* GET home page. */
router.get('/', function(req, res) {
var cursor = database.find();
cursor.each(function(err, data) {
assert.equal(err, null);
if (data != null) {
var vvz = "vestnikverejnychzakazek";
var praha = "zakazky.praha.eu";
var id = data["_id"];
var zdroj = data["zdroj"];
if (zdroj.indexOf(vvz) > -1) {
if ((data["cpv"] == null) || (data["predpokladana_hodnota"] == null)) {
limiter.submit(getCPV, id, zdroj, null);
// getCPV(id, zdroj);
} else {
// console.log("we're good");
return
}
} else if (zdroj.indexOf(praha) > -1) {
// console.log("pha");
}
} else {
// callback();
}
});
var getCPV = function(id, zdroj, callback) {
console.log("CPV started");
var zdroj = zdroj.replace("http://", "https://");
console.log("zdroj: " + zdroj);
var cpv = [];
var retryWrapper = function(retries) {
var retries; // I added this
if (retries === 3) {
return;
} else if (retries === undefined) {
retries = 0;
} else if (retries > 0) {
console.log("trying again");
}
request(zdroj, function(err, resp, data) {
if (err) {
console.log(err);
return retryWrapper(retries + 1);
}
var $ = cheerio.load(data);
var predpokladnaHodnota = $("[id*='Hodnota1_']").first().attr("value");
$("[id*='HlavniSlovnik']").each(function() {
cpv.push(this.attribs.value);
});
// let's check what we've got is actual data
if (cpv.length === 0) {
return
} else {
// send it off
writeCPV(id, "cpv", cpv)
}
if (predpokladnaHodnota == undefined || predpokladnaHodnota == null) {
return
} else {
// send it off
writeCPV(id, "predpokladana_hodnota", predpokladnaHodnota)
}
callback();
});
}; // end of retryWrapper
retryWrapper();
};
var writeCPV = function(id, key, value) {
id = ObjectID(id);
(function() {
console.log("starting DB write 1");
database.update({
"_id": id
}, {
$set: {
[key]: value
}
}, function(err, results) {
if (err) {
console.log("error in Mongo DB: \n------------------------\n" + err);
}
console.log("Mongo success!:\n ----------------------\n" + results);
// callback();
});
})();
};
// send the browser we're done
res.sendStatus(200);
});
// ---------------------
module.exports = router;
Here is a sample document from the DB including the fetched keys:
{
"_id": ObjectId("568d91396912101c1007ab4e"),
"cena": 1636363,
"cena_celkem": 1500000,
"cena_dopocitano": false,
"created": "2015-04-07T13:45:10.420739",
"datum_zadani": "2015-02-16",
"dodavatel": "/api/v1/dodavatel/381836/",
"druh_rizeni": "/api/v1/druh_rizeni/1116/",
"id": 1312587,
"modified": "2015-04-18T14:22:10.765733",
"nazev": "Pohostinství",
"pocet_nabidek": 2,
"podporeno_eu": true,
"popis": "Kurzy v oblasti pohostinství (formou profesní kvalifikace)",
"ramcova_smlouva": true,
"resource_uri": "/api/v1/zakazka/1312587/",
"skupina": "490648-ISVZUS_2011",
"typ_zakazky": "/api/v1/typ_zakazky/193/",
"zadavatel": "/api/v1/zadavatel/131528/",
"zdroj": "http://www.vestnikverejnychzakazek.cz/en/Form/Display/568547",
"zdroj_nazev": "isvzus.cz",
"cpv": ["80000000-4", "80400000-8", "", "", ""],
"predpokladana_hodnota": "1 500 000,00"
}
Sample URL being requested:
http://www.vestnikverejnychzakazek.cz/en/Form/Display/568547
This has been up here for a bit but in case anyone else stumbles upon this hopefully this will help someone!
The limiter is calling getCPV and it does call the callback at the end of the sequence, however, there are some conditional statements in retryWrapper that would allow an early return and as a result, never call the callback. The limiter will get piled up until it fires, so always make sure the callback will get fired in all of the scenarios.

How to stub a nodejs "required" constructor using sinon?

I'm writing unit tests for a method that uses the email-templates module like this:
var EmailTemplate = require('email-templates').EmailTemplate;
module.exports = {
sendTemplateEmail: function (emailName, data, subject, to, from) {
var template = new EmailTemplate(__dirname + "/../emails/" + emailName);
data.from = FROM;
data.host = config.host;
return template.render(data)
.then(function (result) {
return mailer.sendEmail(subject, to, from, result.html, result.text);
})
.then(function () {
log.info(util.format("Sent %s email to %s. data=%s", emailName, to, JSON.stringify(data)));
return Promise.resolve();
})
.catch(function (err) {
return Promise.reject(new InternalError(err, "Error sending %s email to %s. data=%s", emailName, to, JSON.stringify(data)));
});
}
};
The unit test looks like this:
var assert = require("assert"),
sinon = require("sinon"),
Promise = require("bluebird"),
proxyquire = require("proxyquire");
describe('mailer#sendTemplateEmail', function () {
var templates,
template;
beforeEach(function() {
templates = {
EmailTemplate: function(path) {}
};
template = {
render: function(data) {}
};
sinon.stub(templates, "EmailTemplate").returns(template);
});
it("should reject immediately if template.render fails", function () {
const TO = {email: "user1#example.com", first: "User"};
const FROM = {email: "user2#example.com", first: "User"};
const EMAIL_NAME = "results";
const SUBJECT = "Results are in!";
const DATA = {
week: 10,
season: "2015"
};
var err = new Error("error");
var mailer = proxyquire("../src/mailer", {
"email-templates": templates
});
sinon.stub(template, "render").returns(Promise.reject(err));
return mailer.sendTemplateEmail(EMAIL_NAME, DATA, SUBJECT, TO, FROM)
.then(function () {
assert.fail("Expected a rejected promise.");
})
.catch(function (err) {
assert(err.message === "error");
assert(mailer.sendEmail.notCalled);
});
});
};
The problem I'm encountering is on the first line of the sendTemplateEmail function which instantiates a new EmailTemplate object. The EmailTemplate constructor being called points to the non-stub EmailTemplate function defined in the beforeEach, rather than the sinon stub created on the last line of the beforeEach. If I evaluate the require('email-templates').EmailTemplate statement, however, it correctly points to the sinon stub. I'd prefer not to have to change my code to call the require statement inline like:
var template = new require('email-templates').EmailTemplate(__dirname + "/../emails/" + emailName);
Is there any way to accomplish the stub the way I'm intending?
You can inject your dependency when you construct your mailer - exp:
function mailer(options) {
options = options || {};
this.email_template = options.email_template;
}
Then in the sendTemplateEmail function - use the email_template member.
Also - not sure about your mailer code - but if you need your mailer to act as a singleton in your code (and it isn't already) - you can add this to your mailer:
module.exports = {
getInstance: function(emailTemplate) {
if(this.instance === null){
this.instance = new mailer(emailTemplate);
}
return this.instance;
}
}
Then when you require your mailer you can use the syntax:
var template = new require('email-templates').EmailTemplate(__dirname + "/../emails/" + emailName);
var mail = mailer.getInstance(template);
This way your application (unit test framework or your actual/real-world application) will determine the type of mailer that will be used for the lifetime of the process.

How do I expect a function to be called with specific args with sinon, mocha, and chai in nodejs?

I have been having a problem trying to make sure Q.ninvoke is called with the args I am passing in. I am new to testing with Sinon, Mocha and Chai. I have been trying everything I have found online for 2 days now and I still cant get my test pass. What am I doing wrong?
This is my code under test.
var cuid = require('cuid');
var fs = require('fs');
var Q = require('q');
var AWS = require('aws-sdk');
var S3 = new AWS.S3();
module.exports = {
initialize: initialize
};
function initialize(options) {
return Q.nfcall(fs.readFile, options.path).then(function (file) {
var fileParams = {
Bucket: options.bucket,
Key: options.name,
Body: file,
ContentType: options.contentType
};
return Q.ninvoke(S3, 'upload', fileParams).then(function(data){
return data.Location;
});
});
}
Here is my test.
describe.only('when a file is read successfully', function() {
var spy;
beforeEach(function() {
spy = chai.spy.on(Q, 'ninvoke');
sinon.stub(Q, 'nfcall').withArgs(fs.readFile, fileParams.path).returns(Q.resolve(file));
});
it('Q.ninvoke should be called with args', function() {
UploadCommand.initialize(fileParams)
expect(spy).to.have.been.called.with(S3, 'upload', params);
});
});
This is the error I am getting.
1) UploadCommand .initialize when a file is read successfully
Q.ninvoke should be called with args:
AssertionError: expected { Spy } to have been called with [ Array(3) ]
try this:
var cuid = require('cuid');
var fs = require('fs');
var Q = require('q');
var AWS = require('aws-sdk');
var S3 = new AWS.S3();
module.exports = {
initialize: initialize
};
function initialize(options) {
return Q.nfcall(fs.readFile, options.path).then(function (file) {
var fileParams = {
Bucket: options.bucket,
Key: options.name,
Body: file,
ContentType: options.contentType
};
return Q.ninvoke(S3, 'upload', fileParams);
});
}
note in particular that you should return a promise from your initialize function. then in the test:
describe.only('when a file is read successfully', function() {
var spy;
beforeEach(function() {
spy = chai.spy.on(Q, 'ninvoke');
sinon.stub(Q, 'nfcall').withArgs(fs.readFile,fileParams.path).returns(Q.resolve(file));
});
it('Q.ninvoke should be called with args', function(done) {
UploadCommand.initialize(fileParams).then(function(data) {
expect(spy).to.have.been.called.with(S3, 'upload', params);
done();
});
});
});
a couple of other things to note, in your main application code, you will also want to chain your initialize function to a 'then' function, and in the body of that then function is where the rest of your application code should go. also, the 'done' callback is the way you tell mocha that it is an asynchronous test.
Mike I was able to get it working finally thanks to you. I really appreciate it! Here is the final test.
describe.only('when a file is read successfully', function() {
beforeEach(function() {
sinon.stub(Q, 'nfcall').withArgs(fs.readFile, fileParams.path).returns(Q.resolve(file));
sinon.stub(Q, 'ninvoke').withArgs(S3, 'upload', params).returns(Q.resolve('url'));
chai.spy.on(Q, 'ninvoke')
});
it('Q.ninvoke should be called with args', function(done) {
UploadCommand.initialize(fileParams).then(function(data) {
expect(Q.ninvoke).to.have.been.called.with(S3, 'upload', params);
done();
});
});
});
You can use sinon to stub as shown below
let yourFunctionStub
yourFunctionStub= sinon.stub(yourClass, "yourFunction");
yourFunctionStub.withArgs(arg1, arg2, arg3).returns(resultYouWant);
if this a promise you can return
....returns(Promise.resolve(resultYouWant));
if for argument you are not clear you can you
sinon.match.any
Hope this helps you. :)

Resources