Mocha - Failed looping through array that gets initialized in before() - node.js

Consider this test setup:
var expect = require('chai').expect;
var fs = require('fs');
describe('my test', function() {
var dataArr;
// get data
before(function(done) {
fs.readFile('myArray.json', 'utf8', function (err, data) {
dataArr = JSON.parse(data);
done();
});
});
// test data
dataArr.forEach(function(val, index) {
it('Testing element no ' + (index+1), function(done) {
expect(val).to.be.cool;
done();
});
});
});
This test returns the error Cannot read property 'forEach' of undefined because the loop is executed before before().
I found this approach on SO which uses nested its, but that seems to be a dirty workaround (googling mocha nested it proves that).
Isn't there any clean solution for this not so uncommon (?) problem?

Tests are defined synchronously. It is test function that is postponed by asynchronous done call, not dataArr.forEach.
In this case asynchronous readFile provides no benefits. It would be simpler with:
before(function() {
const data = fs.readFileSync('myArray.json', 'utf8');
dataArr = JSON.parse(data);
});
And there's no need for readFile at all. Reading the file isn't a part of a test and should be performed once. Node.js natively supports importing JSON files:
var expect = require('chai').expect;
var dataArr = require('./myArray.json');

You are executing the forEach method out of mocha's hands, you should move all your logic inside it method
So update your code like below:
it('Testing elements', function(done) {
expect(dataArr).all.to.be.cool;
});

I don't think any any cleaner solutions exist.
In your example, dataArr is undefined at the time it is evaluated, that is the member forEach doesn't yet exist because dataArr is not yet an array at that point. It would be defined inside the it()'s callback function scope as the it() callback function is called after the 'before' callback.
So the nested "describe-it" workaround works because it fixes the evaluation order such that dataArr is defined at that point.
So the already mentioned workaround is like this:
var assert = require('assert');
var fs = require('fs');
describe('my test', function() {
var dataArr;
// get data
before(function(done) {
dataArr = ["cool", "cool", "cool"]; // or fs.readFile or require
done();
});
// test it
it('test after data setup', function() {
describe('the tests', function() {
// now the dataArr is defined!
dataArr().forEach(function(val, index) {
it('Testing element no ' + (index+1), function(done) {
assert.equal(val, "cool");
done();
});
});
});
});
});
On the other hand if you don't alter the dataArr, you could evaluate it at once on spot of the declaration like:
var assert = require('assert');
var fs = require('fs');
describe('my test', function() {
const dataArr = ["cool", "cool", "cool"]; // or dataArr = require('testfile.json');
before(function(done) {
done();
});
// test it
// now the dataArr is defined!
dataArr().forEach(function(val, index) {
it('Testing element no ' + (index+1), function(done) {
assert.equal(val, "cool");
done();
});
});
});

Related

Node.js sinon stubbing a function in parallel executions causes failed tests

I have 2 test cases which test the same function just taking 2 different executions paths, so to illustrate:
MyClass.prototype.functionBeingTested = function() {
if (this.check1()) {
this.isCheck1Called = true;
} else if (this.check2()) {
this.isCheck1Called = false;
} else {
...
}
};
My 2 test cases are as follow:
it('should take check1() execution path', function() {
var myClass= new MyClass({}, {}, {});
var check1Stub sinon.stub(MyClass.prototype, 'check1');
check1Stub.returns(true);
myClass.functionBeingTested();
myClass.isCheck1Called.should.equal(true);
});
it('should take check2() execution path', function() {
var myClass= new MyClass({}, {}, {});
var check2Stub sinon.stub(MyClass.prototype, 'check2');
check2Stub.returns(true);
myClass.functionBeingTested();
myClass.isCheck1Called.should.equal(false);
});
Now by default, check1() returns false so I don't stub it in the second test case, but by the time the second case is running, the check1() function stub is still active and causes the second case to enter the execution path of the first case as-well, making the second case test fail.
I understand it's a problem of test running in parallel and the first sinon stub still being used by the first test case, is there anyway I can solve this problem?
At the end of the first test, you should restore the original method (which is always a good thing, to prevent tests from being influenced by previous tests):
check1Stub.restore()
Or, alternatively, you can use a Sinon sandbox to run each test in:
describe('MyClass', function() {
beforeEach(function() {
this.sinon = sinon.sandbox.create();
});
afterEach(function() {
this.sinon.restore();
});
it('should take check1() execution path', function() {
var myClass = new MyClass({}, {}, {});
// `this.sinon` is the sandbox
var check1Stub = this.sinon.stub(MyClass.prototype, 'check1');
check1Stub.returns(true);
myClass.functionBeingTested();
myClass.isCheck1Called.should.equal(true);
});
it('should take check2() execution path', function() {
var myClass = new MyClass({}, {}, {});
var check2Stub = this.sinon.stub(MyClass.prototype, 'check2');
check2Stub.returns(true);
myClass.functionBeingTested();
myClass.isCheck1Called.should.equal(false);
});
});
(See mocha-sinon, which does exactly the same)

mocha/chai return undefined

I have a working node script and am now trying to learn mocha/chai to add some unit tests.
//Module Code
var path = require('path');
var recursive = require('recursive-readdir');
function findData(folderPath) {
recursive(folderPath, function (err, files) {
return files;
});
};
module.exports.findData = findData;
My mocha test code:
var expect = require('chai').expect;
describe('checkData', function () {
var findData = require('../custom_modules/findData').findData;
var path;
before (function () {
path = '/Data'
});
it('should have 53 files in array', function () {
expect(findData(path)).to.have.lengthOf(53);
})
});
However, it always fails because the return seems to be undefined. So i stripped my module code back to test a return true and that worked.
So it must the the asynchronous nature of the recursive module so I then tried add in callbacks to my module code:
var path = require('path');
var recursive = require('recursive-readdir');
function findData(folderPath, cb) {
recursive(folderPath, function (err, files) {
cb(null, files);
});
};
module.exports.findData = findData;
But that didn't work either.
The weird thing is if I run node index.js i get the list of files.
Can anyone explain to me how this code can work normally but when I try to use mocha/chai to test I get undefined?
Thanks
EDITED:
So based on what #Louis said in the comments I have added a callback to the function.
describe('checkData', function () {
var findData = require('../custom_modules/findData').findData;
var path;
var files;
before (function () {
path = '/Users/tjmartin/Documents/OSData/OpenNames/Data'
});
it('should have 53 files in array', function () {
expect(findData(path, function(results) {
files = results;
})).to.have.lengthOf(53);
})
});
But this still returns an undefined.
First up, I would log the err result in your findData implementation. Even if only during development, so you can easily see if any errors are reported (you may be doing this already, just wanted to mention it).
As you have spotted, one of the main causes of problems for you is that the callback is asynchronous. So you can't simply return it from your findData method as in your original example.
Next, I wouldn't hard-code the path as you have in the before function. Instead use a local path, so that the test can be run as part of your CI (if you ever have one) or even so you can grab it on another machine and have it work there.
before(function() {
path = './tests/TestData';
});
In your revised example, although you are using a callback you are testing the return result still. You need to alter your test to use the result of the callback.
it('should have 53 files in array', function(done) {
findData(path, function(results) {
expect(results).to.have.lengthOf(53);
done();
});
});

Supertest routes with mock services

UPDATE
I updated the code below to reflect my solution. It was rather confusing to figure it out but hopefully it will help someone else too.
I'm trying to figure out how to test my routes. The issue I'm running into is, when I make the GET request my node-googleplaces service calls out to the google api. Is there a way to mock out this service so that I can test my route and just fake the data it returns?
controller.js
'use strict';
var path = require('path'),
GooglePlaces = require('node-googleplaces');
exports.placesDetails = function (req, res) {
var places = new GooglePlaces('MY_KEY');
var params = {
placeid: req.params.placeId,
};
//this method call will be replaced by the test stub
places.details(params, function (err, response) {
var updatedResponse = 'updated body here'
res.send(updatedResponse)
});
};
test.js
var should = require('should'),
//seem weird but include it. The new version we're making will get injected into the app
GooglePlaces = require('node-googleplaces');
request = require('supertest'),
path = require('path'),
sinon = require('sinon'),
describe(function () {
before(function (done) {
//create your stub here before the "app" gets instantiated. This will ensure that our stubbed version of the library will get used in the controller rather than the "live" version
var createStub = sinon.stub(GooglePlaces, 'details');
//this will call our places.details callback with the 2nd parameter filled in with 'hello world'.
createStub.yields(null, 'hello world');
app = express.init(mongoose);
agent = request.agent(app);
done();
});
it('should get the data', function (done) {
agent.get('/api/gapi/places/search/elmersbbq')
.end(function (err, res) {
if (err) {
return done(err);
}
console.log(res.body)
done();
});
});
})
The only way I'm thinking about to do it is to change your method to:
exports.placesDetails = function (req, res, places)
create additional method:
exports.placesDetailsForGoogle = function (req, res) {
exports.placesDetails(req, res, new GooglePlaces('MY_KEY'));
}
and write a test that executes placesDetails, passing properly mocked 'places' object. You'll test placesDetails logic with this and at the same time you'll have comfy function to be used in actual code without need to actualy instantiate GooglePlaces object every time.

How can I stub a Promise such that my test can be run synchronously?

I am trying to unit test a module by stubbing one of its dependencies, in this case the UserManager
A simplified version of the module is as follows:
// CodeHandler
module.exports = function(UserManager) {
return {
oAuthCallback: function(req, res) {
var incomingCode = req.query.code;
var clientKey = req.query.key;
UserManager.saveCode(clientKey, incomingCode)
.then(function(){
res.redirect('https://test.tes');
}).catch(function(err){
res.redirect('back');
}
);
}
};
};
I'm stubbing the UserManager's saveCode function which returns a Promise such that it returns a resolved Promise, but when I assert that res.redirect has been called, alas at the time of the assertion res.redirect has not yet been called.
A simplified version of the unit test is:
// test
describe('CodeHandler', function() {
var req = {
query: {
code: 'test-code',
key: 'test-state'
}
};
var res = {
redirect: function() {}
};
var expectedUrl = 'https://test.tes';
var ch;
beforeEach(function() {
sinon.stub(UserManager, 'saveCode').returns(
new RSVP.Promise(function(resolve, reject){
resolve();
})
);
sinon.stub(res, 'redirect');
ch = CodeHandler(UserManager);
});
afterEach(function() {
UserManager.saveCode.restore();
res.redirect.restore();
});
it('redirects to the expected URL', function(){
ch.oAuthCallback(req, res);
assert(res.redirect.calledWith(expectedUrl));
})
});
How can I properly stub the promise such that the method under test behaves synchronously?
I've worked out a solution using sinon-stub-promise.
describe('CodeHandler', function() {
var req = {
query: {
code: 'test-code',
key: 'test-state'
}
};
var ch;
var promise;
var res = {
redirect: function() {}
};
beforeEach(function() {
promise = sinon.stub(UserManager, 'saveCode').returnsPromise();
ch = CodeHandler(UserManager);
sinon.stub(res, 'redirect');
});
afterEach(function() {
UserManager.saveCode.restore();
res.redirect.restore();
});
describe('can save code', function() {
var expectedUrl = 'https://test.tes';
beforeEach(function() {
promise.resolves();
});
it('redirects to the expected URL', function(){
ch.oAuthCallback(req, res);
assert(res.redirect.calledWith(expectedUrl));
});
});
describe('can not save code', function() {
var expectedUrl = 'back';
beforeEach(function() {
promise.rejects();
});
it('redirects to the expected URL', function(){
ch.oAuthCallback(req, res);
assert(res.redirect.calledWith(expectedUrl));
})
})
});
This works perfectly.
Well, the easiest thing would be not to stub it to run synchronously at all since that might change execution order and use Mocha's built in promises support (or jasmine-as-promised if using jasmine).
The reason is there can be cases like:
somePromise.then(function(){
doB();
});
doA();
If you cause promises to resolve synchronously the execution order - and thus output of the program changes, making the test worthless.
On the contrary, you can use the test syntax:
describe("the test", () => { // use arrow functions, node has them and they're short
it("does something", () => {
return methodThatReturnsPromise().then(x => {
// assert things about x, throws will be rejections here
// which will cause a test failure, so can use `assert`
});
});
});
You can use the even lighter arrow syntax for single lines which makes the test even less verbose:
describe("the test", () => { // use arrow functions, node has them and they're short
it("does something", () =>
methodThatReturnsPromise().then(x => {
// assert things about x, throws will be rejections here
// which will cause a test failure, so can use `assert`
});
);
});
In RSVP, you can't set the scheduler as far as I know so it's quite impossible to test things synchronously anyway, other libraries like bluebird let you do it at your own risk, but even in libraries that let you do it it's probably not the best idea.

'then' function is not never getting called by Mocha for assertion checking

I have written the following test case in Mocha, where the my code uses Q module.
var expect = require("chai").expect;
var utils = require("../Utils.js");
var utils1 = require("../a.js");
var sinon = require('sinon');
var request = require('requestretry');
var querySys = require('../b.js');
var Q = require("q");
describe("Sample", function () {
var results;
describe("#get()", function () {
before(function (done) {
done();
});
it("equal", function () {
var deferred = Q.defer();
var responseData = {};
responseData.code = 200;
responseData.data = [{a:1,b:2}];
deferred.resolve(responseData);
//querySys1 method uses Q promises. That is how I stubbed the response.
sinon.stub(querySys, 'querySys1').returns(deferred.promise);
//get function internally calls querySys1. Hence I have stubbed that.
results = utils1.get(specification);
results.then(function (data) {
//Here I do see data coming as {in:1, out:1}. But still the test case is passing when compare it with {}.
console.log(data);
//Ideally, it should have failed. But it is passing.
expect(data).to.be.equal({});
});
});
after(function (done) {
done();
})
});
});
So, if you see, I am trying to do assertion check in results.then part. I have printed the response, which I am receiving. That is coming as expected. But I am intentionally trying to match with wrong value, but test case is still passing.
Since your test does not include a callback, execution runs through the main block and declares the test as passing without waiting for the result of the then function. You need to let mocha know to wait for the callback:
it("equal", function (done) {
...
results.then(function (data) {
console.log(data);
expect(data).to.be.equal({});
return done();
});
});
When I changed results.then to results.done, test started failing as expected. Can someone say whether this is the right approach.

Resources