I have this case where I think I want to have nested it() test cases in a Mocha test. I am sure this is wrong, and I don't see any recommendations to do what I am doing, but I don't really know of a better way at the moment -
basically, I have a "parent" test, and inside the parent test there's a forEach loop with all the "child" tests:
it('[test] enrichment', function (done) {
var self = this;
async.each(self.tests, function (json, cb) {
//it('[test] ' + path.basename(json), function (done) {
var jsonDataForEnrichment = require(json);
jsonDataForEnrichment.customer.accountnum = "8497404620452729";
jsonDataForEnrichment.customer.data.accountnum = "8497404620452729";
var options = {
url: self.serverURL + ':' + self.serverPort + '/event',
json: true,
body: jsonDataForEnrichment,
method: 'POST'
};
request(options,function (err, response, body) {
if (err) {
return cb(err);
}
assert.equal(response.statusCode, 201, "Error: Response Code");
cb(null);
});
//});
}, function complete(err) {
done(err)
});
});
as you can see, two separate lines are commented out - I want to include them so that I can easily see the results of each separate test, but then I have this awkward situation of firing the callback for the test alongside the callback for async.each.
Has anyone seen this time of situation before and know of a good solution where the tester can easily see the results of each test in a loop?
Don't nest it calls. Call them synchronously.
Nested it calls are never okay in Mocha. Nor are it calls performed asynchronously. (The test can be asynchronous, but you cannot call it asynchronously.) Here's a simple test:
describe("level 1", function () {
describe("first level 2", function () {
it("foo", function () {
console.log("foo");
it("bar", function () {
console.log("bar");
});
});
setTimeout(function () {
it("created async", function () {
console.log("the asyncly created one");
});
}, 500);
});
describe("second level 2", function () {
// Give time to the setTimeout above to trigger.
it("delayed", function (done) {
setTimeout(done, 1000);
});
});
});
If you run this you won't get the nested test bar will be ignored and the test created asynchronously (delayed) will also be ignored.
Mocha has no defined semantics for these kinds of calls. When I ran my test with the latest version of Mocha at the time of writing (2.3.3), it just ignored them. I recall that an earlier version of Mocha would have recognized the tests but would have attached them to the wrong describe block.
I think the need for dynamic tests are relatively common (data-driven tests?), and there is common use for dynamic it and test cases.
I think it could be easier to manage testcase completion if tests could be executed in series. This way you wouldn't have to worry about managing nested async done's. Since request is async (i'm assuming), your test cases will still mainly be executing concurrently.
describe('[test] enrichment', function () {
var self = this;
_.each(self.tests, function (json, cb) {
it('[test] ' + path.basename(json), function (done) {
var jsonDataForEnrichment = require(json);
jsonDataForEnrichment.customer.accountnum = "8497404620452729";
jsonDataForEnrichment.customer.data.accountnum = "8497404620452729";
var options = {
url: self.serverURL + ':' + self.serverPort + '/event',
json: true,
body: jsonDataForEnrichment,
method: 'POST'
};
request(options,function (error, response, body) {
if (error) {
cb(error);
}
else{
assert.equal(response.statusCode, 201, "Error: Response Code");
cb(null);
}
done();
});
});
}
});
Related
I'm kind of new to JavaScript/Node.js so bear with me. Also my english may not be that good.
I'm trying to write a Node.js module module.js with functions that do some long-running work. Kind of like this:
var exec = require('child_process').exec;
module.exports.myFunction1 = function(callback) {
// this function runs for like 3 seconds
exec('long running shell command' ,function(err,stdout,stderr) {
callback(stdout);
})
};
module.exports.myFunction2 = function(callback) {
// this function runs for like 1 second
exec('long running shell command' ,function(err,stdout,stderr) {
callback(stdout);
})
};
Now, I also have a main.js where I invoke these functions:
var module = require('./module.js');
var output1 = module.myFunction1();
var output2 = module.myFunction2();
My first problem is that my functions return undefined. I understand that this is because the exec function runs asynchronously and therefore the function returns before exec has finished. This is basically what I want but how can I tell my function that it should only callback when exec has finished?
I also don't want the functions to block node.js when I invoke them in my main.js. So basically, my output of the above code would be...
Output myFunction2: Output2
Output myFunction1: Output1
...because myFunction2() finishes faster than myFunction1().
I tried many, many solutions I found online but nothing seems to work properly.
Thank you very much in advance!
--- EDIT ---
Ok, I'm having a somewhat correct solution. Right now my code looks like this:
module.js
var Q = require('q');
require('shelljs/global')
module.exports = {
myFunction1: function () {
var deferred = Q.defer();
var result = exec('long running command', {silent:true}).output.toString();
if (ok) {
deferred.resolve(result);
}
else {
deferred.reject('Error');
}
return deferred.promise;
},
myFunction2: function () {
var deferred = Q.defer();
var result = exec('long running command', {silent:true}).output.toString();
if (ok) {
deferred.resolve(result);
}
else {
deferred.reject('Error');
}
return deferred.promise;
}
}
My main.js lloks like this now:
var module = require('./module');
module.myFunction1()
.then(function (result) {
console.log('Result 1: ' + result);
})
.fail(function (error) {
console.log(error)
});
module.myFunction2()
.then(function (result) {
console.log('Result 2: ' + result);
})
.fail(function (error) {
console.log(error)
});
And I get the expected output:
Result 1: Output that myFunction1() generated
Result 2: Output that myFunction2() generated
My Problem now is, that myFunction1() always logs before myFunction2(), even if myFunction2() finished first. Did I understood something wrong about Promises? Shouldn't myFunction2() return immediately after it finished?
Your functions take callbacks. Those parameters are functions which are called on completion, which makes it easy to do
var exec = require('child_process').exec;
module.exports.myFunction1 = function(callback) {
// this function runs for like 3 seconds
exec('long running shell command' ,function(err,stdout,stderr) {
callback(stdout);
})
};
module.myFunction1(function(stdout){
console.log("Output myFunction1: " + stdout);
});
Using a callback, in your case, is the simplest solution but you should be aware that there are other patterns to deal with asynchronous executions. Here's a good overview. For example, a popular solution, especially interesting when you have to chain asychronous continuations, is to use promises, which allow
var exec = require('child_process').exec;
module.exports.myFunction1 = function() {
return new Promise(function(resolve, fail){
// this function runs for like 3 seconds
exec('long running shell command' ,function(err,stdout,stderr) {
if (err) fail(err);
else resolve(stdout, stderr);
});
});
};
module.myFunction1()
.then(function(stdout){
console.log("Output myFunction1: " + stdout);
})
.then(module.myFunction2)
.then(function(stdout){
console.log("Output myFunction2: " + stdout);
})
At first, I would suggest you to handle errors (err, stderr) in your modules. As you can see, your functions takes one argument which is callback. If your asynchronous function runs, the callback function is called. So you can use it like this:
module.myFunction1(function(stdout) {
console.log("Output myFunction1: " + stdout);
module.myFunction2(function(stdout2) {
console.log("Output myFunction2: " + stdout2);
});
});
exec function also takes callback function (with first argument error err - error first callbacks). There are other options how to handle flow control of asynchronous code (e.g. library async). You can also learn about Promises which is today's alternative to error first callbacks.
Callback functions don't return values directly... what you need is to setup what will happen when value will get read. Something like this:
my_function(what_will_happen_when_my_function_will_get_finished());
exectly:
myFunction1(function(data){console.log('hello! I've finished, and received: '+data);});
The below files are within a project created with the generator-webapp generator for Yeoman. My script is working in the browser and returns the information from the JSON file but does not work in the test most of the time. The test succeeds sometimes which means the test hits a hiccup long enough to allow the getJSON to return the data in time. In my search, I found various resources, here are two sources that sounded like they should be solving my issue: a stackoverflow question and a blog.
They both involve passing the done parameter to the it function and then calling done(); after executing the test. At least, that is my understanding but it still isn't working. I feel I am missing something really obvious.
Here is app/scripts/source-data.js.
var source = (function() {
var sourceData = null;
_loadData();
function _loadData(done) {
$.getJSON("app/data/source.json", function(data) {
sourceData = data;
});
}
function getData() {
return sourceData;
}
return {
getData: getData
};
})();
Here is test/spec/source-data.js.
(function() {
describe("Source Data", function() {
describe("Data for External Modules", function() {
it("returns the source data from a file", function(done){
expect(source.getData().spec[0].name).to.equal("Spec");
done();
});
});
});
})();
I tried altering where done() is called as my understanding was that done() tells Mocha to go ahead with the rest of the test after the getJSON is done. However, at this point, this was just trial and error as I found I had no real understanding.
...
var data = source.getData();
done();
expect(data.spec[0].name).to.equal("Spec");
...
Following the above, I tried setTimeout in the main script but that still didn't work! Even if it did, I don't think I should use setTimeout in this situation, but properly wait for the resolution of getJSON.
You should use callback.
For example:
var source = (function() {
var sourceData;
function getData(done) {
if(sourceData){
done(sourceData);
} else {
$.getJSON("app/data/source.json", function(data) {
sourceData = data;
done(data);
});
}
}
return {
getData: getData
};
})();
and test will be like this
(function() {
describe("Source Data", function() {
describe("Data for External Modules", function() {
it("returns the source data from a file", function(done){
source.getData(function(sourceData){
expect(sourceData.spec[0].name).to.equal("Spec");
done();
});
});
});
});
})();
I have a function as shown below:
function test(parms) {
var self = this;
return this.test2(parms)
.then(function (data) {
if (data) {
return ;
}
else {
return Bluebird.delay(1000)
.then(self.test.bind(self, parms));
}
}.bind(self));
};
I am trying to write unit tests for this function. I am using sinon.stub to mock the functionality of the function test2.
I wrote a test case where test2 returns true and therefore the test function successfully completes execution. However I want a test case where on the first instance test2 returns false, it waits for delay and next time test2 returns true. For that I wrote my test case as below:
var clock;
var result;
var test2stub;
var count = 0;
before(function () {
clock = sinon.useFakeTimers();
//object is defined before
test2stub = sinon.stub(object,"test2", function () {
console.log("Count is: " + count);
if (count === 0) {
return (Bluebird.resolve(false));
}
else if (count === 1) {
return (Bluebird.resolve(true));
}
});
clock.tick(1000);
object.test("xyz")
.then(function (data) {
result = data;
});
clock.tick(1000);
count = count + 1;
clock.tick(1000);
});
after(function () {
test2stub.restore();
clock.restore();
});
it("result should be undefined. Check if test2 returned false first & true next",
function () {
expect(result).to.be.undefined;
});
In the logs it shows that count has value 0 only.
The code of test is actually incorrect. It never returns data on success. It returns undefined. The function should return data on success otherwise you won't be able to use it as parameter for next .then handler
.then(function (data) {
if (data) {
return data;
}
Next you make wrong assumptions about function test. It will NEVER return undefined. The function is rather dangerous and will call itself forever in an endless chain of promises until it squeezes out any not-null data from test2.
One shouldn't launch test code in before or beforeEach section. before and after are meant to prepare the environment like faking timers and then restoring them.
One reason for calling tested code in the it handler is because promises should be handled differently. The handler should accept a parameter which indicates that the test will be asynchronous and the the test engine gives it a timeout (usually 10 secs) to complete. The test is expected to either call done() to indicate test is successful or call done(error) if it failed and there is an error object (or expect threw an exception).
Also you should move the fake timer after the async operation started. In your code actually the first clock.tick is useless.
There is a trick with using fakeTimers. You can move time manually however it doesn't move on its own. For the first tick it works well. The promise is executed. However upon returning .delay(1000) promise, there will be no command to move the time forward. So, to finish the test correctly (not to modify tested code) you have also to stub Bluebird.delay
I would change the stubs implementation and do something like this
describe("test2", function(){
beforeEach(function(){
clock = sinon.useFakeTimers();
test2stub = sinon.stub(object,"test2", function () {
console.log("Count is: " + count);
return (Bluebird.resolve((count++) > 0));
});
var _delay = Bluebird.delay.bind(Bluebird);
bluebirdDelayStub = sinon.stub(Bluebird,"delay", function (delay) {
var promise = _delay(delay);
clock.tick(1000);
return promise;
});
})
it("should eventually return true", function (done) {
object.test("xyz")
.then(function (data) {
expect(data).to.be.true;
expect(count).to.equal(2);
done();
})
.catch(function(err){
done(err);
});
clock.tick(1000);
});
after(function () {
test2stub.restore();
clock.restore();
bluebirdDelayStub.restore();
});
})
PS I verified this code under Node.js 0.10.35 and Bluebird 2.9.34
I'm starting to use Jasmine for unit-testing of a JavaScript library that relies heavily on promises. I need to fail a test case asynchronously, and would like to write something like the following:
describe("An async test suite", function () {
it("should fail asynchronously", function (done, fail) {
var promise = myLibraryCall();
promise.then(done, function(reason) { fail(reason); });
});
});
However, there is nothing like a fail call available from what I can see. And I can't throw an exception in the asynchronous error case because it's not caught by Jasmine - all I get is an eventual generic timeout. What is the best way to address this?
Short of modifying Jasmine itself, the simple solution is to create a wrapper around a combination of expect and a custom matcher to fail with a given message.
function endTestAfter(promise, done) {
var customMatchers = {
toFailWith: function () {
return {
compare: function (actual, expected) {
return {
pass: false,
message: "Asynchronous test failure: " + JSON.stringify(expected)
};
}
}
}
};
jasmine.addMatchers(customMatchers);
promise.done(done, function (reason) {
expect(null).toFailWith(reason);
done();
});
}
This yields the following test suite code:
describe("An async test suite", function () {
it("should fail asynchronously", function (done, fail) {
var promise = myLibraryCall();
endTestAfter(promise, done);
});
});
I'm using a function to fetch data from webapi. Basicly using $.ajax.
I'm now testing it with waits() like this:
describe('xxxxxxxxxxxxxxxxxxxxx', function () {
var r;
it('fetchFilter', function () {
runs(function () {
model.fetch(opts)
.done(function(data) {
r = data;
});
});
waits(2000);
runs(function () {
expect(r[0].gender).toBeDefined();
});
});
});
The problem is:
It's not guaranteed that waits(2000) will do the job well. Due to various reasons(network connections, algorithm efficiency of the api it self, etc.), I may have to waits(5000) or more, or for some models waits(500) is enough. And the most annoying thing is that it's all out of control.
A lot of waits() makes the test-specs-runs waste a lot of time waiting. The time of running the whole suite is too long to accept.
Is there some best practice of doing there kind of things?
PS: I know that unit test should not be applied to some function that relies on webapi or database. But I'm working with a single-page-js-heavy-webapp. The data fetching process is as important as how I will consume them with js models.
waitsFor() will wait for a specified latch callback to return true (it will try many time every few ms). It will also raise an exception if the specified timeout (5000ms in this case) is exceeded.
describe('xxxxxxxxxxxxxxxxxxxxx', function () {
var r, fetchDone;
it('fetchFilter', function () {
runs(function () {
model.fetch(opts).done(function(data) {
r = data;
fetchDone = true;
});
});
waitsFor(function() {
return fetchDone;
}, 5000);
runs(function () {
expect(r[0].gender).toBeDefined();
});
});
});
Check the Jasmine docs for more info on waitsFor() and runs()
The following solution allows you to wait no more than really necessary but still you have to define max timeout you suppose to be enough.
The waitsFor takes the function and waits until it returns true or the timeout passed as the last argument expired. Otherwise it fails.
Supposing the thing you need to wait for is that r[0] is defined at all, it could be:
waitsFor(
function() { return r[0]; },
'the data should be already set',
5000);
As per jasmine 2.5, you can pass an extra paramater for it("scenario", callback, timeout)
describe('xxxxxxxxxxxxxxxxxxxxx', function (done) {
var r, fetchDone;
it('fetchFilter', function () {
runs(function () {
model.fetch(opts).done(function(data) {
r = data;
fetchDone = true;
});
});
setTimeout(function() {
done();
}, 9000);
runs(function () {
expect(r[0].gender).toBeDefined();
});
});
},10000);