I've been struggling with unit test for 2 days now and there is something I can't achieve regarding async test. I'm new to unit test and I don't understand why this doesn't work.
I have a file login.js that calls a $.getJSON(url, data, function) and returns a string with the status of login ("success" or "fail").
The call to $.getJSON uses mockjax to get the data (it wraps an ajax call).
The login function works ok, when called from a test webpage using jQuery click event.
But now I'm trying to run headless test using Qunit and PhantomJS.
It seems the problem is that the test is not waiting for the $.getJSON call to get the result (even when using a timeout).
Any clues?
Here is the code.
login.js
var login = function(user, pass){
$.getJSON("loginURL", {"user":user, "pass":pass}, function(response){
if(response.status === "success"){
//do something
return "success";
}
else{
//do something else
return "fail";
}
});
};
test.js
test("Test login", function(){
var user = "user1", pass = "pass1";
var done = assert.async();
var result = login(user, pass);
setTimeout(function(){
assert.equal(result, "success", "expect login succeded");
done();
},1000);
});
In the test result I get:
Expected: "success"
Result: undefined
Your login function should be asynchronous, because its result depends on a response from server.
So let's rewrite the function like this:
function login(user, pass, done) {
$.getJSON(..., function (res) {
done(res.status == 'success')
})
}
Then you can test like this (assuming mocha):
describe('...', function () {
it('login()', function (done) {
login('user', 'pw', function (isLoggedIn) {
assert(isLoggedIn)
done()
})
})
})
After rewrite my login function as #ayanami suggested the code works smoothly and it looks like this:
login.js
var login = function(form, cb){
$.getJSON("loginURL", {"user":user, "pass":pass}, function(response){
if(response.status === "success"){
//do something
}
else{
//do something else
}
cb(response.status);
});
};
test.js (qunit)
test( "Testing login", function( assert ) {
var done = assert.async();
var cb = function(param){
assert.equal(param,"success", "Expect getJSON/login to success");
done();
};
login(user, pass,cb);
});
Related
I checked a few thread in StackOverflow, but nothing works for me.
I have this request call and I need it to try to send the request until it succeed (but if it fails, it has to wait at least 3 seconds):
sortingKeywords.sortVerifiedPhrase = function(phrase) {
var URL = "an API URL"+phrase; //<== Obviously that in my program it has an actual API URL
request(URL, function(error, response, body) {
if(!error && response.statusCode == 200) {
var keyword = JSON.parse(body);
if(sortingKeywords.isKeyMeetRequirements(keyword)){ //Check if the data is up to a certain criteria
sortingKeywords.addKeyToKeywordsCollection(keyword); //Adding to the DB
} else {
console.log("doesn't meet rquirement");
}
} else {
console.log(phrase);
console.log("Error: "+ error);
}
});
};
Here's the weird part, if I call the same phrases in a row from the browser, it works almost without errors (it usually states: rate limit time esceeded).
Appreciate your help.
Thanks in advance.
Here is a working program that I've written for this request. It sends the request via a function, if the request fails, it returns error handler and calls the function again.
If the function succeeds the program returns a promise and quits executing.
NOTE: if you enter an invalid url the programs exits right away, that's something that's have to do with request module, which I like to be honest. It lets you know that you have an invalid url. So you must include https:// or http:// in the url
var request = require('request');
var myReq;
//our request function
function sendTheReq(){
myReq = request.get({
url: 'http://www.google.com/',
json: true
}, (err, res, data) => {
if (err) {
console.log('Error:', err)
} else if (res.statusCode !== 200) {
console.log('Status:', res.statusCode)
} else {
// data is already parsed as JSON:
//console.log(data);
}
})
}
sendTheReq();
//promise function
function resolveWhenDone(x) {
return new Promise(resolve => {
myReq.on('end', function(){
resolve(x)
})
myReq.on('error', function(err){
console.log('there was an error: ---Trying again');
sendTheReq(); //sending the request again
f1(); //starting the promise again
})
});
}
//success handler
async function f1() {
var x = await resolveWhenDone(100);
if(x == 100){
console.log("request has been completed");
//handle other stuff
}
}
f1();
On error run this code
setTimeout(function(){}, 3000);
See this https://www.w3schools.com/jsref/met_win_settimeout.asp
Also you can make a code like this
var func1 = function(){};
setTimeout(func1, 3000);
I have some code that handles outgoing and incoming data. When I send a request, I expect some kind of an answer from an external source. If no answer has arrived after a timeout of 1500ms, I return something to an error callback and end the request. That all looks something like this:
this.sendRequest = function(sendRequestDevice, sendRequestFID, sendRequestParam, sendRequestAction, sendRequestData,
sendRequestReturnCB, sendRequestErrorCB) {
if (!this.isConnected) {
if (sendRequestErrorCB !== undefined) {
sendRequestErrorCB(VacuumSystem.ERROR_NOT_CONNECTED);
}
return;
}
// Packet creation
var sendRequestAddress = this.getDeviceAddress(sendRequestDevice);
var sendRequestPacket = pack(sendRequestAddress, sendRequestAction, sendRequestParam, sendRequestData);
// var sendRequestSEQ = this.getSequenceNumberFromPacket(sendRequestHeader);
// Sending the created packet
if (sendRequestDevice.expectedResponse.length === 0) {
// Setting the requesting current device's current request
sendRequestDevice.expectedResponse.push({
FID: sendRequestFID,
timeout: setTimeout(this.sendRequestTimeout.bind
(this, sendRequestDevice, sendRequestErrorCB), this.timeout),
returnCB: sendRequestReturnCB,
errorCB: sendRequestErrorCB
});
this.serialport.write(sendRequestPacket);
} else {
setTimeout(this.sendRequest.bind(this, sendRequestDevice, sendRequestFID, sendRequestParam,
sendRequestAction, sendRequestData, sendRequestReturnCB, sendRequestErrorCB ), 1000)
}
};
this.sendRequestTimeout = function (timeoutDevice, timeoutErrorCB) {
// if the response is not received in 'this.timeout' ms, throw an error
clearTimeout(timeoutDevice.expectedResponse[0].timeout);
timeoutDevice.expectedResponse.splice(0, 1);
if (timeoutErrorCB !== undefined){
timeoutErrorCB(VacuumSystem.ERROR_TIMEOUT);
console.log('Error Timeout');
}
return;
};
I want to test this behavior with a unit test using mocha and chai. Basically I just want to assert that the error callback is called after 1.5s with the argument VacuumSystem.ERROR_TIMEOUT. What I tried is:
describe('behavior', function() {
beforeEach(function () {
returnCallback = sinon.spy();
errorCallback = sinon.spy();
// this function calls the sendRequest function with the needed parameters
PPT100.getPressure(returnCallback, errorCallback);
});
it('should return a Timeout Error when no response is received', function (done) {
setTimeout(function () {
expect(errorCallback.callCount).to.equal(1);
sinon.assert.calledWith(errorCallback, VacuumSystem.ERROR_TIMEOUT)
done();
}, PPT100.ipcon.timeout);
});
});
I know that the error is returned since I can see the log message 'Error Timeout', but expect fails for the sinon spy. What am I doing wrong here?
Here's the code:
vm.saveData = function(data) {
demoService.saveData(data, function(response) {
if (response.status === 200) {
principal.identity(true).then(function() {
$state.reload();
return toastr.success('Success');
});
}
return toastr.error('Failure');
});
}
On getting success response from the api, it should display only the 'success' message. But, instead it displays the 'failure' message first and then the 'success' message. What am I doing wrong? Do I have to put a timeout or is there something which I'm missing here?
If the status is 200 then you set up a promise to call success later on.
Regardless of what the status is (because it is outside of the if and you haven't used an else) you always call error.
Presumably you just want to move return toastr.error('Failure'); into an else
That's not how you setup promises. Promises uses .then(). You are simply using passing the function in as a callback.
vm.saveData = function(data) {
demoService
.saveData(data)
.then(success, error);
function success(response) {
principal.identity(true).then(function() {
$state.reload();
return toastr.success('Success');
});
}
function error(response) {
return toastr.error('Failure');
}
};
Many systems such as AJAX send multiple messages to indicate progress in the task. You want to ignore the earlier messages. The failure message is from the earlier events while the action is incomplete.
I found out my mistake. Adding 'return' resolved the issue.
'return principal.identity(true).then(function(){
//do something here
});'
vm.saveData = function(data) {
demoService.saveData(data, function(response) {
if (response.status === 200) {
return principal.identity(true).then(function() {
$state.reload();
return toastr.success('Success');
});
}
return toastr.error('Failure');
});
}
I have got a test failing because of a timeout using Mocha.
I do call the "done()" function but it does not seem to work for some reason.
My test currently looks like this:
var express = require('express');
var expect = require('chai').expect;
var mocha = require('mocha');
var calendar = require('./../Server/calendarDatabase');
describe("Calendar", function () {
describe("Database", function () {
it("should get stuff from the database", function (done) {
return calendar.Connect()
.then(function () {
return calendar.getAll();
})
.then(function (returnValue) {
expect(returnValue.count).to.equal(5); //This should fail, my database records are 20+
done();
});
});
});
});
Where my calendar.Connect() and calendar.getAll() are both promises:
var express = require('express');
var sql = require('mssql');
var config = require('./../config');
var calendarDbConnection = {};
calendarDbConnection.Connect = function() {
return sql.connect(config.mssql);
}
calendarDbConnection.getAll = function () {
var promise = new sql.Request()
.query('select * from CalendarTable')
.catch(function (err) {
console.log(err.message);
});
return promise;
}
module.exports = calendarDbConnection;
However while running my test, I get following output:
When I call the done() after my last then(), the function gets resolved but the outcome of my test does not. The number of lines I get from the database are over 20 and I check if they are equal to 5. So, my test should fail, but it does not.
//previous code
.then(function (returnValue) {
expect(returnValue.count).to.equal(5); //This should fail, my database records are 20+
});
done();
//...
So this last outcome passes my test, while it should not.
What am I missing here? I am calling the callback function but then my expected outcome is not correct.
Since You are returning a Promise from the test, you should not pass done as an argument:
Alternately, instead of using the done() callback, you may return a Promise. This is useful if the APIs you are testing return promises instead of taking callbacks.
Although you can pass done to catch call as stated above, it seems more convenient to get rid of done and just return a promise.
it("should get stuff from the database", function () {
return calendar.Connect() // returns Promise, so no `done`
.then(function () {
return calendar.getAll();
})
.then(function (returnValue) {
expect(returnValue.count).to.equal(5);
});
});
Only pass done in the catch.
then(....) { ... done(); }.catch(done);
I am just getting into unit testing and currently exploring the Mocha, Chai, Sinon setup, testing this in a browser.
I have a javascript block that makes an ajax call to the server like so
PromptBase.prototype.fetchAndSetTemplateString = function() {
var deferred = $.Deferred();
$.ajax({
url: "/someurl",
type: "GET"
})
.done(function(response) {
if (response.status.toLowerCase() === "success") {
deferred.resolve(response.templateString);
}
});
return deferred.promise();
};
PromptBase.prototype.fetchPromptTemplate = function() {
var promise = this.fetchAndSetTemplateString();
$.when(promise).done(function(promptHtml) {
//set the templateString to promptHtml here
});
};
//Register a custom event on document to fire ajax request.
$(document).on("customevent", function(event) {
promptInstance.fetchPromptTemplate(promptParams.fetchTemplateOnEvent);
});
Now I have a test case that looks like this using fakeServer
describe("TemplateFetch", function() {
before(function() {
this.server = sinon.fakeServer.create(); // and other setup
});
after(function() {
this.server.restore(); // and other clean up
});
it("should fetch template string from server, when fetchTemplateEvent is fired", function() {
var expectedTemplateString = "templateStringFromServer";
var templateAjaxUrl = '/someurl';
//faking a server response
this.server.respondWith("GET", templateAjaxUrl,
[200, {"Content-Type": "application/json"},
'{"status": "success", "templateString": "'+ expectedTemplateString +'"}']);
// Now trigger even that fetches the template
$(document).trigger("customevent");
// This calls the ajax done function, which resolves the promise
this.server.respond();
//debugger shows that templateString is set here
expect(this.instance.templateString).to.eqaul("something");
});
});
However my test outputs the timeout error
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
So the fakeServer.respond() call ensures that the ajax->done function is called resulting in the promise being resolved, And when I debug I can see that the expect test should match the two strings. How can I fix the time out issue here? I have also tried adding a done callback but to no avail.
Any input is appreciated. Thanks for reading.