how to handle sinon.stub().throws() in unit test by Sinon JS - angularjs

Am trying to invoke fail condition in my snippet.
But when I use sinon.stub().throws() method It shows me error.
Am unable to handle it in code.
Here is my snippet:
login() {
let loginData = this.loginData;
return this.authService.login(loginData).then(userData => {
let msg = `${this.niceToSeeYouAgain} ${userData.email}!`;
this.userAlertsService.showSuccessToast(msg);
this.navigationService.afterLoggedIn();
//above lines are covered in test cases
}, errorInfo => {
// below line are needed to test
this.userAlertsService.showAlertToast(errorInfo);
});
}
**And here is my unit-test snippet: **
it('.login() - should throw exception - in failure case', sinon.test(() => {
let errorInfo = "some error";
let stub = sinon.stub(authService, 'login').throws();
let spy1 = sinon.spy(controller.userAlertsService, 'showAlertToast');
//call function
controller.login();
// $timeout.flush();
// expect things
console.log(stub.callCount, stub.args[0]);
}));
Please let me know what am doing wrong

You need to wrap the function that you know is going to fail, and then call it. e.g.
it('handles errors in methodThatCallsAnotherFailingMethod', function() {
error = new Error("some fake error");
sandbox.stub(SomeObject, "doSomething").throws(error);
call = function() {
// methodThatCallsAnotherFailingMethod calls SomeObject.doSomething()
methodThatCallsAnotherFailingMethod();
};
expect(call).to.throw(Error);
});
When testing (or spying on) other stuff in methodThatCallsAnotherFailingMethod you can do this in your test:
try {
call();
} catch (error) {
expect(MySpy).to.have.been.calledWith(error);
}

This question is a month old as of this answer, but I encountered a similar error, and Google hasn't yielded any explanation for this behavior. I wanted to test the failure branch of my login as well, and stub.throws() actually threw the error (causing the test to fail) instead of rejecting the login promise. If anyone knows why this happens, I'd appreciate it.
In any case, this is what worked for me:
let d = Q.defer(); // Or whichever promise library you use
d.reject(); // Force the promise to fail
let stub = sinon.stub(authService, 'login').returns(d.promise); // Should do what you want
// The rest of the test

Related

lwc - how to call a function from within an lwc callback

Havent been able to reconcile this challenge to the docs :/ Hoping someone can point out to me why when this lwc renders (successfully) and it receives an event via its empApi subscription it throws a 'handleGoNext is not defined' runtime error. I appreciate the function is not visible, but I'm not able to construct things such that a resulting function call is able to be made successfully. Calling this.handleGoNext() doesnt work either. Any pointers would be most appreciated!
handleGoNext(){
// *** this is the logic Im hoping to call ***
};
// Initializes the component
connectedCallback() {
if(this.subscription = {}){
// Callback invoked whenever a new event message is received
const messageCallback = function (response) {
handleGoNext(); // *** THIS IS THE CALL THAT BREAKS THINGS ***
};
// Invoke subscribe method of empApi. Pass reference to messageCallback
subscribe(this.channelName, -1, messageCallback).then((response) => {
// Response contains the subscription information on subscribe call
console.log(
'Subscription request sent to: ',
JSON.stringify(response.channel)
);
this.subscription = response;
});
}
}
what happens when you do subscribe(this.channelName, -1, this.handleGoNext)?
Here's my function from around Easter time, didn't check recently if it still works.
isSubscribed = false;
subscription = {};
replayId;
#track data = [];
subscribe(id) {
const handleMessage = (response) => {
let val = Object.assign({}, response.data.event, response.data.payload);
this.replayId = Math.max(this.replayId ?? 0, val.replayId);
/* do not use this.data.push(val);
it doesn't play well with datatable. Use the spread operator or JSON.parse/JSON.stringify trick.
Plus we want new rows to be added on top of the list.`
*/
this.data = [val, ...this.data];
};
subscribe(this.channel, id, handleMessage).then((response) => {
this.subscription = response;
this.isSubscribed = true;
});
}

How do you push function calls onto an array without calling them in Node JS for using Q?

I'm looking to create an array of functions to call dynamically, which will be later used in the Q.all([]) promise call.
For example;
//data is previously generated
var promiseArray = [];
for (var i = 0; i < data.length; i++){
promiseArray.push(functionCall(data[i]))
}
Q.all(promiseArray).then(function(){
//Do something
})
How would I push to the array without calling the function until the Q.all statement? I don't want to call it in the for loop as it will not catch any errors and I can't process the response further.
EDIT:
So to clarify my problem (as I don't think I was as clear as I should have been), here is a solution for a static data length of say 3;
//data is previously generated
var data = [12432432,4324322392,433324323];
//Each function call can happen in parallel or series as its an external POST to an API
//I'm not bothered about speed for this application (as its low throughput) and can wait a few seconds for each
// response
//FunctionCall returns a promise
functionCall(data[0]).then(function(){
//Log success / failure to mongo
});
functionCall(data[1]).then(function(){
//Log success / failure to mongo
});
functionCall(data[2]).then(function(){
//Log success / failure to mongo
});
//OR
functionCall(data[0]).then(function(){
//Log success/failure to mongo
functionCall(data[1]).then(function(){
//Log success/failure to mongo
functionCall(data[2]).then(function(){
//Log success/failure to mongo
});
});
});
But I wont know the length of data until runtime
If I understand correctly, you want to call functionCall for an array of items, and have Q.all resolve once all the promises returned by functionCall have completed regardless if they resolve or reject - if you don't care about the results (as you don't seem to in your code) simply handle the rejection in the promise you push - i.e.
var promiseArray = [];
for (var i = 0; i < data.length; i++) {
promiseArray.push(functionCall(data[i]).then(function(result) {
// log success
return logToMongoFunction(result);
}, function(error) {
// log failure
return logToMongoFunction(error);
}).catch(function(error) {
// catch and ignore any error thrown in either logToMongoFunction above
return;
}));
}
Q.all(promiseArray).then(function () {
//Do something
});
Note: the above can be simplified to
Q.all(data.map(function (item) {
return functionCall(item).then(function(result) {
// log success
return logToMongoFunction(result);
}, function(error) {
// log failure
return logToMongoFunction(error);
}).catch(function(error) {
// catch and ignore any error thrown in either logToMongoFunction above
return;
});
})).then(function() {
//Do something
});
the edited question suggests you can perform the actions in series also - in series it would be
data.reduce(function(promise, item) {
return promise.then(function() {
return functionCall(item).then(function(result) {
// log success
return logToMongoFunction(result);
}, function(error) {
// log failure
return logToMongoFunction(error);
}).catch(function(error) {
// catch and ignore any error thrown in either logToMongoFunction above
return;
});
});
}, Promise.resolve()).then(function() {
// all done
});
instead of Promise.resolve() you could use whatever Q has as an equivalent that creates a resolved promise
logToMongoFunction would log to mongo and needs to return a promise if you need to wait for that to finish before processing the next data item. If you do not need to wait for the mongo logging to complete then there's no need for that function to return a promise
i will recommend using Promise.mapSeries or async library for this because its very easy to catch errors. One more thing looping using a for loop doesnt seems to be good approach if you have database calls in the callback because that might flush the calls to the database and node.js can have memory issues or node.js wont be able to entertain any other request because it will be busy entertaining the request in the for loop. so its always good to run loop serially or limit the numer of parallel executions at a time.
please see example below
This will run Array serially one at a time when 1st one completes execution next will be called
async.eachOfSeries(data, function(dataInstance, key, next) {
functionCall(dataInstance).then(function(){
next();
}).catch(funtion(err){
next(err);
})
}, function() {
//iteration completed
});
OR
async.eachOfSeries(data, function(dataInstance, key, next) {
functionCall(dataInstance, function(err , result){
if(err)
{
console.log(err);
next(err);
}
else
next();
});
}, function() {
//iteration completed
});

Unable to Test lines after .then in sinon js unit testing

Am a newborn in sinon.js, mocha, chai.
Am trying to write unit-test by using above libraries.
Project Front-End is in AngularJS with the combination of ECMASCRIPT6.
From the docs of Sinonjs I understand nothing! It's complicated.
Here is my JS Snippet:
login() {
let loginData = this.loginData;
return this.authService.login(loginData).then(userData => {
let msg = `${this.niceToSeeYouAgain} ${userData.email}!`;
this.userAlertsService.showSuccessToast(msg);
this.navigationService.afterLoggedIn();
}, errorInfo => {
this.userAlertsService.showAlertToast(errorInfo);
});
}
And here is my UNIT-TEST snippet:
it('.login() - should load user data and showSuccessToast and call navigationService afterLoggedIn', sinon.test(() => {
let msg = `Nice to see you again ${userData.email}!`;
let loginData ={
email: "sarfaraz#walkover.in",
password: "designer99",
remember: true
}
let stub = sinon.stub(authService, 'login').resolves(userData);
// let spy1 = sinon.spy(controller.userAlertsService, 'showSuccessToast');
//call function
controller.login();
$timeout.flush();
// expect things
expect(stub.called).to.eq(true);
// restore
stub.restore();
}));
But unfortunately am stucked.
My code not runs after .then lines.
am expecting it should throws error. cause I have not spies on userAlertsService and navigationService.
Please let me know what am doing wrong

How to roll back changes when there is an error in a promise chain

In my angular app I want to make changes to several locations in my firebase with a mix of transactions and set. I have written a promise chain with a little help. Now I need to handle any errors that may occur.
In the event of an error on any of the promises I would want to roll back any changes made in firebase (the successful promises) and alert the user to the failure.
Current code below
$scope.addNewPost = function() {
var refPosts = new Firebase(FBURL).child('/posts').push();
// Get tags into array for incrementing counters
var tags = $scope.post.tags.split(', ');
var allPromises = [];
// Iterate through tags and set promises for transactions to increment tag count
angular.forEach(tags, function(value, index){
var dfd = $q.defer();
var refTag = new Firebase(FBURL).child('/tags/' + value);
refTag.transaction( function (current_value) {
return current_value + 1;
}, function(error, committed, snapshot) {
if (committed) {
dfd.resolve( snapshot );
} else {
dfd.reject( error );
}
});
allPromises.push( dfd.promise );
});
// Add promise for setting the post data
var dfd = $q.defer();
refPosts.set( $scope.post, function (error) {
if (error) {
dfd.reject(error);
} else {
dfd.resolve('post recorded');
}
});
allPromises.push( dfd.promise );
$q.all( allPromises ).then(
function () {
$scope.reset(); // or redirect to post
},
function (error) {
// error handling goes here how would I
// roll back any data written to firebase
alert('Error: something went wrong your post has not been created.');
}
);
};
So what I need to know is how do I roll back any changes that happen to my firebase data in the event that one of these promises fail. There could be any number of updates happening in firebase. (for example: 3 tags being incremented via transaction and the post data being set)
How would I write the failure function to calculate what was successful and undo it? If this is this even possible.
--------------- sub question from original post has been solved ---------------
Also how do you force errors? I've tried setting a variable like below but it doesn't seem to work, is there something wrong with my .then?
refPosts.set( $scope.post, function (error) {
var forceError = true;
if (forceError) {
dfd.reject(forceError);
} else {
dfd.resolve('post recorded');
}
allPromises.push( dfd.promise );
});
There are two instances of this line, and they are both in the wrong place:
allPromises.push( dfd.promise );
In the first block, it should be in the last statement in the forEach callback, not in the transaction callback.
In the second block, it should be after the call to set(), not in the callback.
The way your code is written now, $q.all() is getting an empty array of promises. That could also be what's interfering with the forceError test you're attempting.

Caught in Restangular promises

I've got this code:
var me = Restangular.one('object', 1).get({params:params});
me.then(function (data) {
$scope.me = data;
$scope.loadsubobject();
});
$scope.loadsubobject = function () {
$scope.me.getList("subobject")
.then(function(subobject) {
//the code never gets here
console.log('loaded subobjects', subobjects);
$scope.subobjects = $scope.subobjects.concat(subobject.results);
if (data.next){
$scope.next = data.next;
}
},function(response){
console.log('Error with response:', response.status)
});
when I try to debug the code It seems that after calling the $scope.me.getList("subobject")It returns to the first thenand never getting to the second then, the one that actually process the subobjects I need.
Any clue out of call back hell?
I verified that the server does return the correct answer
How can I fix that? be glad for help with this
turned out to be a completely different issue, the json returned wasn't "flat", so I need to use a responseExtractor as explained here

Resources