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();
});
});
});
});
})();
Related
I have created a function to get text from a file using an url. The function uses $.get() of jQuery to fetch the file. The function works fine but the problem here is $.get() is asynchronous so the order of the output is not predictable i tried changing it to synchronous but it freezes the page completely i have tried waiting for it to respond thinking i would take time but it didn't work.
Here's my code.
var File = (function () {
return {
GetTextFromFile:function(filePath) {
console.log ("Start")
$.get({
url:filePath,
async:true
}, function (data) {
console.log(data)
});
console.log ("End")
}
}
})();
This function outputs
Start
End
'Content_of_the_file'
This creates of problem because i cannot return the content of the file since it's not loaded yet because of the asynchronous get function. So is there any way to tell the function to wait till the $.get() has returned the content of the file.
Using async await we can make asynchronous to work in sync mode.
var File = (function () {
return {
GetTextFromFile: async function(filePath) {
console.log ("Start")
data = await $.get({
url:filePath,
async:true
}, function (data) {
return data
});
console.log(data)
console.log ("End")
return data
}
}
})();
await File.GetTextFromFile()
I have node code that fetches the list of websites to crawl, and on iterating through the result, it calls a crawling function written using phantom. But before the crawling function returns the result, the loop is iterating number of times and hence number of calls to crawling function which is not able to handle it. I need immediate answer for my issue.
Please somebody take me out of this well.
my main page code
db.fetch_serviceEntity(function(serviceEntityData){
if(serviceEntityData!=""){
serviceEntityData.forEach(function(item){
console.log(item.website_url);
db.fetch_entityId(item.id,function(entityId){
crawler.getCount(item.website_url, item.name, function(rCount){
console.log("number of fresh reviews to crawl : ", parseInt(rCount) - parseInt(item.review_count));
if(rCount > item.review_count){
fetchReviews(item.website_url, entityId.id, parseInt(rCount) - parseInt(item.review_count), function(){
db.updateReviewCount(item.id, rCount, function(){
process.exit(0);
});
});
}
});
});
};
});
}
else {
console.log("No websites to crawl/database error");
}
process.exit(0);
});
my crawl function is here
crawler.prototype.crawl = function(webUrl, callback){
console.log(webUrl);
this.driver.create({ path: require('phantomjs').path }, function (err, browser) {
return browser.createPage(function (err,page) {
return page.open(webUrl, function (err,status) {
console.log("opened site? ", status);
page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', function (err) {
// Wait for a bit for AJAX content to load on the page. Here, we are waiting 5 seconds.
setTimeout(function () {
return page.evaluate(function () {
//Get what you want from the page using jQuery.
var reviews = [];
$('li div.review').each(function () {
if($(this).find('div.ypassport').text()){
var d = new Date($(this).find('span.rating-qualifier').text().trim());
var temp = {
id : $(this).attr('data-review-id'),
entity_id : "",
user_id : $(this).attr('data-signup-object').split(":")[1],
}
reviews.push(temp);
}
});
return {
reviews: reviews
};
}, function (err,result) {
browser.exit();
callback(result);
});
}, 5000);
});
});
});
});
};
I am using node-phantom-simple for writing crawling function
my problem here is -> because for loop makes number of calls to it, crawl function giving me error that some or other object not created.
for example in the code it says "createpage is not a function of undefined" hence the meaning is browser object not created.
sometimes it says "open is not a function of undefined " hence the "page" object not creating.
You have async functions, but if you process.exit(0) when you return from your first function, all database connections are dropped and no db.updateReviewCount is called. So you get more or less arbitrary results, depending on how's the one who closes first.
(Beside that, the code is a callback hell. Maybe you want to create smaller functions and then chain them with a library like async or co or even by hand.)
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();
});
});
}
});
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);
I'm trying to write some tests with Jasmine, but now have a problem if there are some code is asynchronous in beforeEach.
The sample code looks like:
describe("Jasmine", function() {
var data ;
beforeEach(function(){
console.log('Before each');
getSomeDataFromRemote(function(res){
data = res;
});
});
it("test1", function() {
expect(data).toBe(something);
console.log('Test finished');
});
});
You can see, in the beforeEach, I want to get some data from remote, and assign it to the data asynchronously.
But in the test1, when I try to verify:
expect(data).toBe(something);
The data is undefined, because getSomeDataFromRemote has not finished yet.
How to fix it?
Just like the async stuff within an it you can use the runs and waitsFor in your beforeEach:
define( 'Jasmine' , function () {
var data ;
beforeEach(function(){
runs( function () {
getSomeDataFromRemote(function(res){
data = res;
});
});
waitsFor(function () { return !!data; } , 'Timed out', 1000);
});
it("test1", function() {
runs( function () {
expect(data).toBe(something);
});
});
});
Although I'm going to assume that it's because this was test code I think you should probably have the getSomeDataFromRemote call inside your it as that's actually what you're testing ;)
You can see some larger examples in some tests I've written for an async API here: https://github.com/aaronpowell/db.js/blob/f8a1c331a20e14e286e3f21ff8cea8c2e3e57be6/tests/public/specs/open-db.js
Jasmine 2.0
Be careful because in the new Jasmine 2.0 this is going to change and it will be mocha style. You have to use done() function in beforeEach() and it(). For example, imagine you want to test if a page exists and is not empty, in a LAMP server, using jQuery $.get. First you need to add jQuery to the SpecRunner.html file, and in your spec.js file:
describe('The "index.php" should', function() {
var pageStatus;
var contents;
beforeEach(function (done) {
$.get('views/index.php', function (data, status) {
contents = data;
pageStatus = status;
done();
}).fail(function (object, status) {
pageStatus = status;
done();
});
});
it('exist', function(done) {
expect(status).toBe('success');
done();
});
it('have content', function(done) {
expect(contents).not.toBe('');
expect(contents).not.toBe(undefined);
done();
});
});
As you can see, you pass the function done() as a parameter for beforeEach() and it(). When you run the test, it() won't be launched until done() has been called in beforeEach() function, so you are not going to launch the expectations until you have the response from the server.
The page exists
If the page exists we capture the status and the data from the response of the server, and we call done(). Then we check if the status is "success" and if the data is not empty or undefined.
The page does not exist
If the page does not exist we capture the status from the response of the server, and we call done(). Then we check if the status is not "success" and if the data is empty or undefined (that must be because the file does not exist).
In this case I typically stub the asynchronous call to respond immediately.
I'm not sure if you've seen it or not, but here is some documentation about asynchronous testing with Jasmine.