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?
Related
Given two api endpoints in my php backend:
Long Function Endpoint: /_api/_long_function &
Status Endpoint: /_api/_status_long_function
In the LongFunction I write session data and in StatusFunction I read them and output them in json. I think it is not relevant to the question but I could show the session getting and setting as well if needed.
I now wanted to start the long function with an ajax call and then periodically request the status with a second ajax call.
The JS Code
$(document).on("click", "#submitSummoner", function () {
...INIT VARS...
/**
* Periodically check Session Status
*/
var to,
clearTime = false,
called = 0,
set_delay = 1000,
callout = function () {
console.log('Called SessionCheck ' + called);
$.get($url_session, {
my_data: my_data
})
.done(function (response) {
if (called === 0) {
console.log('Called LongFunction');
$.get($url, {
my_data, my_data
}).done(function (data) {
console.log('Finished Request');
if (to) {
clearTimeout(to);
clearTime = true;
}
}).fail(function () {
if (to) {
clearTime = true;
clearTimeout(to);
}
console.log('Failed Request');
});
}
called++;
// This is the output I need
console.log('Current Status: '+response.status);
})
.always(function () {
if (clearTime === false) {
to = setTimeout(callout, set_delay);
}
});
};
callout();
});
Unfortunately what happens now is this:
Console Output
1: Called SessionCheck 0
2: Called LongFunction (immediately after)
3: Current Status: Whatever (immediatley)
4: Called SessionCheck 1 (immidately)
LONG PAUSE UNTIL LONG FUNCTION FINISHED
5: Current Status: Finished
after Finished the setTimout gets cleared as expected.
But why does the the setTimeout not get called every 1000ms altough the long function takes 10000ms+?
It seems like the second SessionCheck waits for the Long Check to finish, I don't know why? Is it possible that the server "hangs", CPU/RAM seem fine during the longFunction. Anything else that could "lock" it
Even it the php session check function crashes, shouldn't the function still try it again after 1000ms?
Any hint appreciated!
Solution
I figured it out, the session wasn't saving properly in the LongFunction: https://codingexplained.com/coding/php/solving-concurrent-request-blocking-in-php
I've re-written your solution using jQuery promises to simulate the get requests, and it works as you desired. I believe this proves the issue is on the back-end, that the "short" get request is not resolving the 2nd time. Please provide the code that resolves that request.
var to,
clearTime = false,
called = 0,
set_delay = 1000,
callout = function () {
console.log('Called SessionCheck ' + called);
var shortGet = $.Deferred();
shortGet
.done(function () {
if (called === 0) {
var longGet = $.Deferred();
console.log('Called LongFunction');
longGet.done(function (data) {
console.log('Finished Request');
if (to) {
clearTimeout(to);
clearTime = true;
}
}).fail(function () {
if (to) {
clearTime = true;
clearTimeout(to);
}
console.log('Failed Request');
});
setTimeout(function() {
longGet.resolve();
}, 10000);
}
called++;
// This is the output I need
console.log('Current Status: still going');
})
.always(function () {
if (clearTime === false) {
to = setTimeout(callout, set_delay);
}
});
setTimeout(function() {
shortGet.resolve();
}, 200);
};
callout();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Description: I want to read a particular label from the webpage, whose value changes to one of "started", "inprogress", "success", "Error". Once the label value changes to "success" or "Error" there will not be any further changes.
Issue: When I read the label value using javascript in protractor, the text value of the label is not returned to the calling function; instead it returns 'undefined'. Below is my code, please have a look and let me where the issue is.
CheckColor_Test.js
var commonFunctions = require('../pages/CommonFunctions.js');
describe("Run Test", function () {
it("should stop once the status reached Success or Error", function () {
var processStatus = commonFunctions.refreshTillProcessFinish();
expect(processStatus).toContain('Success','Error');
});
});
CommonFunctions.js
Var CommonFunctions = function(){
var label = element(by.id('Status'));
var refreshStatusBtn = element(by.css('[ng-click="getJob()"]'));
this.getStatusValue = function () {
return label.then(function (headers) {
return headers.getText();
});
};
this.refreshTillRefreshFinish = function () {
var refreshStatusMonitor = function (currentStatus) {
return currentStatus.then(function (Status) {
if (Status == 'Success' || Status.includes("Error")) {
console.log(Status);
return Status;
} else {
refreshStatusBtn.click();
console.log(Status);
browser.sleep(2000);
refreshStatusMonitor (currentStatus);
}
});
};
return refreshStatusMonitor (this.getStatusValue);
};
}
module.exports = new CommonFunctions();
Executing in Protractor:
I have configured protractor in Webstorm, hence I used to run using that.
Expected Result:
The test should get successful and passed
Actual Result:
The test fails with below error.
"C:\Program Files (x86)\JetBrains\WebStorm 2016.1.1\bin\runnerw.exe" "C:\Program Files\nodejs\node.exe" node_modules\protractor\built\cli.js D:\Somesh_HDD\WebstormProjects\ProjectUBET\conf.js
[22:19:59] I/direct - Using ChromeDriver directly...
[22:19:59] I/launcher - Running 1 instances of WebDriver
Spec started
Started
InProgress
Success
Run Test
? should stop once the status reached Success or Error
- Expected undefined to contain 'Success', 'Error'.
**************************************************
* Failures *
**************************************************
1) Run Test should stop once the status reached Success or Error
- Expected undefined to contain 'Success', 'Error'.
Executed 1 of 1 spec (1 FAILED) in 33 secs.
[22:20:36] I/launcher - 0 instance(s) of WebDriver still running
[22:20:36] I/launcher - chrome #01 failed 1 test(s)
[22:20:36] I/launcher - overall: 1 failed spec(s)
[22:20:36] E/launcher - Process exited with error code 1
Process finished with exit code 1
The following return value:
return currentStatus.then(...);
is not the value returned by this statement:
return Status;
In fact, the latter is returned to one of the recursive calls of refreshStatusMonitor which is not captured anywhere.
Because this is asynchronous code involving promises, the return value of currentStatus should be a promise as well, which would bubble up via refreshStatusMonitor, refreshTillRefreshFinish to your test, which then also needs to be adapted to wait for the promise to be fulfilled before expecting anything.
I would also advise against the use of browser.sleep(...) as it completely blocks your JavaScript environment. You could use setTimeout(...) instead.
Here is some untested code which builds on those ideas:
this.refreshTillRefreshFinish = function () {
// create a promise
var deferred = protractor.promise.defer();
var refreshStatusMonitor = function (currentStatus) {
currentStatus.then(function refresh(Status) {
if (Status == 'Success' || Status.includes("Error")) {
// Signal the completion via the promise.
// This triggers the `then` callback in your revised test
deferred.fulfill(Status);
} else {
refreshStatusBtn.click();
console.log(Status);
// Use setTimeout so JavaScript is not blocked here:
setTimeout(function () {
refreshStatusMonitor(currentStatus);
}, 2000);
}
});
};
refreshStatusMonitor(this.getStatusValue);
// Don't wait for the result to happen while blocking everything,
// instead return a custom-made promise immediately
return deferred.promise;
};
Your test should then also take into account that you are dealing with a promise:
it("should stop once the status reached Success or Error", function () {
var processStatus = commonFunctions.refreshTillProcessFinish().then(function () {
expect(processStatus).toContain('Success','Error');
done();
});
}, 20000); // set timeout to 20 seconds
Note that Jasmine has a default timeout of 2 seconds, so you need to provide that extra argument at the end.
NB: Such asynchronous tests are not very suitable for running batches of unit tests.
Is your script able to click on the refresh button recursively?
i have made few changes to your existing script by introducing promises inside the recursive method.Just give a try.
var CommonFunctions = function(){
var label = element(by.id('Status'));
var refreshStatusBtn = element(by.css('[ng-click="getJob()"]'));
this.refreshTillRefreshFinish = function () {
var defer = protractor.promise().defer();
var refreshStatusMonitor = function () {
label.getText().then(function (Status) {
if (Status == 'Success' || Status.includes("Error")) {
defer.fulfill(Status);
} else {
refreshStatusBtn.click();
browser.sleep(2000);
refreshStatusMonitor ();
}
});
return defer.promise;
};
return refreshStatusMonitor ();
};
}
module.exports = new CommonFunctions();
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 doing custom $http service that looks something like this:
angular.factory('myHttp', function($http){
var obj = {};
obj.get = function(path) {
return $http.get(path,{timeout: 5000}).error(function (result) {
console.log("retrying");
return obj.get(path);
});
}
});
The system works fine. It does return the data when success, and retrying when connection fail. However, I am facing problem that it will return to controller when the connection is timeout. How can I prevent it from returning and continue retrying?
You need to use $q.reject. This will indicate that the error handler failed again and the result should populated to the parent error handler - NOT the success handler.
obj.get = function(path) {
return $http.get(path, {
timeout: 5000
}).then(null, function(result) {
console.log("retrying");
if (i < retry) {
i += 1;
return obj.get(path);
} else {
return $q.reject(result); // <-- use $q.reject
}
});
}
See plunker
See the reject.status to determine the timeout
$http.get('/path', { timeout: 5000 })
.then(function(){
// Your request served
},function(reject){
if(reject.status === 0) {
// $http timeout
} else {
// response error
}
});
Please see the following question for a good overview about handling timeout errors:
Angular $http : setting a promise on the 'timeout' config
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);
});