I'm trying to add some mocha testing to a node module I have, but I'm new to it, and my lack of concreteness in terms of callbacks is hampering me.
I have tried to pare things back to the most straightforward example, but it's still not working.
So my main.js is
var async = require('async');
var myObject = {};
myObject.test = function(params) {
async.waterfall([
async.apply(test, params)
],
function(err, result){
if (err) {
console.log('Error: ' + err);
} else {
if (result === 200) {
return result;
}
}
});
};
function test(params, callback) {
if(params) {
callback(null, 200);
}
}
module.exports = myObject;
Then my test file
var assert = require("assert");
var myObject = require('./main');
describe('test', function(){
it("should return 200", function(done){
myObject.test({test: 'such test'}, function(err, res) {
if (err) return done(err);
assert.equal(res, 200);
done(res);
});
})
});
If I just run mocha it times out so I'm suspicious about that! Trying mocha --timeout 15000 also just stalls. Any direction you can provide would be really appreciated!
I got this far using this answer but can't get any further.
OK, I think I sorted it, but would still appreciate some feedback to see if I'm approaching it correctly, rather than just managing to get my test to pass.
var async = require('async');
var myObject = {};
myObject.test = function(params, callback) {
async.waterfall([
async.apply(test, params)
],
function(err, result){
if (err) {
console.log('Error: ' + err);
} else {
if (result === 200) {
callback(result);
}
}
});
};
function test(params, callback) {
if(params) {
callback(null, 200);
}
}
module.exports = myObject;
and the test file is
var assert = require("assert");
var myObject = require('./main');
describe('test', function(){
it("should return 200", function(done){
myObject.test({test: 'such test'}, function(res) {
assert.equal(res, 200);
done();
});
})
});
You fixed your main issue but your code still is broken. When you have an async method that takes a callback, you must always invoke the callback exactly once in all cases or your program's control flow will break. If you write an if/else clause, both branches must invoke the callback function. Both of your if statements above violate the callback contract. Check out understanding error-first-callbacks from The Node Way for a good explanation.
Related
I'm trying to test a function that reads in a file and returns a promise with the file's contents.
function fileContents(){
return new Promise(function(resolve, reject) {
fs.readFile(filename, function(err, data){
if (err) { reject(err); }
else { resolve(data); }
});
});
}
The unit test for the above
describe('Testing fileContents', function () {
afterEach(function () {
fs.readFile.restore();
});
it('should return the contents of the fallBack file', function () {
let fileContents = '<div class="some-class">some text</div>';
sinon.stub(fs, 'readFile').returns(function(path, callback) {
callback(null, fileContents);
});
let fileContentsPromise = fileContents();
return fileContentsPromise
.then(data => {
expect(data).to.eventually.equal(fileContents);
});
});
The above test errs with
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I also tried
describe('Testing fileContents', function () {
afterEach(function () {
fs.readFile.restore();
});
it('should return the contents of the fallBack file', function (done) {
let fileContents = '<div class="some-class">some text</div>';
sinon.stub(fs, 'readFile').returns(function(path, callback) {
callback(null, fileContents);
});
let fileContentsPromise = fileContents();
fileContentsPromise.then(function(data){
expect(data).to.equal(fileContents);
done();
});
});
and got the same error. The function is working in my local site, but I don't know how to write a test for it. I'm new to js. What am I missing?
There are multiple problems with your code. For instance, you redeclare fileContents in your test and assign it a string value, which of course won't work with doing fileContents() in the same test. I'm going to concentrate on two conceptual problems rather than the "duh"-type mistakes like this one.
The two conceptual problems are:
To have fs.readFile call your callback with fake values you must use .yields. Using .returns changes the return value, which you do not use. So stub it like this:
sinon.stub(fs, 'readFile').yields(null, fakeContents);
You are using the .eventually functionality provided by chai-as-promised on a non-promise, but you have to use it on a promise for it to work properly so your test should be:
return expect(fileContentsPromise).to.eventually.equal(fakeContents);
Here's code that works:
const sinon = require("sinon");
const fs = require("fs");
const chai = require("chai");
const chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
const expect = chai.expect;
// We need to have filename defined somewhere...
const filename = "foo";
function fileContents(){
return new Promise(function(resolve, reject) {
fs.readFile(filename, function(err, data){
if (err) { reject(err); }
else { resolve(data); }
});
});
}
describe('Testing fileContents', function () {
afterEach(function () {
fs.readFile.restore();
});
it('should return the contents of the fallBack file', function () {
let fakeContents = '<div class="some-class">some text</div>';
sinon.stub(fs, 'readFile').yields(null, fakeContents);
let fileContentsPromise = fileContents();
return expect(fileContentsPromise).to.eventually.equal(fakeContents);
});
});
I am using pg-promise. i am learnner please excuse if it seems trivial to you. how can i wrtie unit test for. it errors out data is undefined. i have been making connection in js file and export that module.another js file use to query against database and fetch result set. code is working as expected having issue how can i write unit test with mocha and chai.
test1.js
var dbConn= pgp(connUrl);
module.exports = {
getconnect: function () {
return dbConn;
}
};
test2.js
module.exports = {
getData: function (req, res) {
db.getconnect().query(sqlStr, true)
.then(function (data) {
console.log("DATA:", data);
return data;
} } }
unittest.js
describe("Test Cases", function (done) {
it('retrieve response', function (done) {
var req = {};
var res = {};
test2.getData(req, res);
// how would i retrieve value of data from test2.js so i can test
done();
});
});
how would i retrieve "data" value from test2.js inthe unittest.js
Your getData must return the promise. Client code will be able to recognize the moment it's finished(resolved).
module.exports = {
getData: function (req, res) {
return db.getconnect().query(sqlStr, true)
.then(function (data) {
console.log("DATA:", data);
return data;
} } }
test:
describe("Test Cases", function () {
it('retrieve response', function (done) {
var req = {};
var res = {};
test2.getData(req, res).then(function(data){
// test of data returned
done(); // finish test
}).catch(done);// report about error happened
});
});
If you don't need to any process of data in your module, you can to remove whole .then section without any functionality changes.
But if you want to preprocess data - don't forget to return it from each chained .then.
If your test library requires stubs for async stuff, you can use async/await feature to deal with it.
it('retrieve response', async function(){
try {
var data = await test2.getData(req, res);
// test data here
} catch (e) {
// trigger test failed here
}
});
Or stub it, something like this:
var dbStub = sinon.stub(db, 'getConnect');
dbStub.yields(null, {query: function(){/*...*/}});
If you have a function returning promise then you can use await in your tests:
describe("Test Cases", function (done) {
it('retrieve response', async function (done) {
try {
var data = await test2.getData();
// check data constraints ...
...
} catch(err) {
...
}
done(); // finish test
});
});
I want to save 8 objects to a MongoDB database using Mongoose. When the last document is saved, I want to report (i.e. send an event) that all documents have been saved.
The way I'm doing it now is quite messy (especially for increasingly larger amounts of documents I want to save).
This is how I have it now (just for 4 people for this example). Is there a cleaner way you can recommend?
person1.save(function(err, result){
if (err) console.log(err);
else{
person2.save(function(err, result){
if (err) console.log(err);
else{
person3.save(function(err, result){
if (err) console.log(err);
else{
person4.save(function(err, result){
if (err) console.log(err);
else{
done();
}
});
}
});
}
});
}
});
A useful library to coordinate asynchronous operations is async. In your case, the code would look something like this:
var people = [ person1, person2, person3, person4, ... ];
async.eachSeries(people, function(person, asyncdone) {
person.save(asyncdone);
}, function(err) {
if (err) return console.log(err);
done(); // or `done(err)` if you want the pass the error up
});
Using promises and Array.map()
const userPromises = persons.map(user => {
return new Promise((resolve, reject) => {
person.save((error, result) => {
if (error) {
reject(error)
}
resolve(result);
})
})
});
Promise.all(userPromises).then((results) => {
//yay!
//results = [first, second, etc...]
}, (error) => {
//nay!
})
I would recommend to have an array and save with iteration. Will have same performance but code would be cleaner.
You can have
var Person = mongoose.model('Person');
var people = [];
people[0] = new Person({id: 'someid'});
people[0].set('name', 'Mario');
people[1] = new Person({id: 'someid'});
people[1].set('name', 'Mario');
people[2] = new Person({id: 'someid'});
people[2].set('name', 'Mario');
var errors = [];
for(person in people){
people[person].save(function(err, done){
if(err) errors.push(err);
if (person === people.length){ yourCallbackFunction(errors){
if (errors.length!=0) console.log(errors);
//yourcode here
};
}
});
}
The best way is to use async waterfall.
Part of the code snippet might look as below. Please refer the above link.
It breaks the async nature and converts into a one after another process (if I am not wrong).
waterfall([
function(callback){
callback(null, 'one', 'two');
},
function(arg1, arg2, callback){
callback(null, 'three');
},
function(arg1, callback){
// arg1 now equals 'three'
callback(null, 'done');
}
], function (err, result) {
// result now equals 'done'
});
I would use Underscore Each to iterate over an array of People models. This would lead to your code being cleaner and help avoid the "boomerang effect" you are experiencing with your code.
From the documentation:
.each(list, iteratee, [context]) Alias: forEach
Iterates over a list of elements, yielding each in turn to an iteratee function. The iteratee is bound to the context object, if one is passed. Each invocation of iteratee is called with three arguments: (element, index, list). If list is a JavaScript object, iteratee's arguments will be (value, key, list). Returns the list for chaining.
For example:
var people = [person1, person2];
var length = people.length;
var hasError = false;
var doSomething = function() {
console.log("I'm done");
}
var doSomethingElse = function() {
console.log('There was an error');
}
_.each(people, function(person, i) {
if (!hasError) {
person.save(function(err, result) {
if (err) {
console.log(err);
hasError = true;
}
);
if (i === length) {
doSomething();
}
} else {
// There was an error
doSomethingElse();
}
});
I am having trouble getting complete coverage in my testing where I am trying to hit a callback function inside the function I am testing. Here is the function :
CrowdControl.prototype.get = function() {
var options = this.optionsFor('GET');
return q.Promise(function(resolve, reject) {
function callback(error, response, body) {
if (error) {
reject(error);
} else {
resolve(body);
}
}
request(options, callback);
});
};
So I have the function covered off except the function callback :
function callback(error, response, body) {
if (error) {
reject(error);
} else {
resolve(body);
}
}
I cant seem to figure out how to hit this with tests.
Up top I have the request stubbed out like this
var request = sinon.stub();
beforeEach(function() {
CrowdControl = rewire('crowdcontrol');
CrowdControl.__set__({
request: request
});
});
So I'm not sure how I can make it hit the callback and test that. Could use some insight as this is still new to me. Thanks!
So I'm trying a simple test at first something like this -
it("should call callback function.", function() {
crowdControl.get();
//callback should fire?
expect(callback).to.have.been.called;
});
Simple way to achieve what you want is this:
CrowdControl.prototype.get = function(callback) {
callback = callback || function callback(error, response, body) {
if (error) {
reject(error);
} else {
resolve(body);
}
};
var options = this.optionsFor('GET');
return q.Promise(function(resolve, reject) {
callback();
request(options, callback);
});
};
You can now inject the function in when testing (it will use the real implementation if the argument is not supplied).
CrowdControl.get(someFakeFunction);
When you are working with functions that return promise, you have to make your asserts (expect) are inside then(), so it will look something like this:
it("should call callback function.", function(done) {
var callback = sinon.stub();
crowdControl.get(callback).then(function(){
expect(callback).to.have.been.called;
done();
});
in the following code I am expecting the function finalCallBack to be executed once we are done iterating through all elements
var rows = [
{ name: 'first'},
{ name: 'second'}
];
var execForEachRow = function(row, callback){
var studentModel = new StudentModel(row);
studentModel.save(function(err,result){
if (err) { throw err;}
rowsSavedCount++;
});
}
var finalCallBack = function(err){
if (err) { msg = err;} else { msg = rowsSavedCount;}
res.send({"result" : msg});
}
async.each(rows, execForEachRow, finalCallBack);
When i execute the above code, it very successfully inserts data into the mongo collection. However the finalCallBack does not get called.
Any clue what I might be missing here ?
You've missed calling callback in studentModel.save's callback:
studentModel.save(function(err,result){
if (err)
return callback(err);
rowsSavedCount++;
callback(null);
});
Also - throwing Exception is not a good idea - it'll break you whole Express server.