How does one stub promise with sinon? - javascript

I have a data service with following function
function getInsureds(searchCriteria) {
var deferred = $q.defer();
insuredsSearch.get(searchCriteria,
function (insureds) {
deferred.resolve(insureds);
},
function (response) {
deferred.reject(response);
});
return deferred.promise;
}
I want to test following function:
function search ()
{
dataService
.getInsureds(vm.searchCriteria)
.then(function (response) {
vm.searchCompleted = true;
if (response.insureds.length > 100) {
vm.searchResults = response.insureds.slice(0, 99);
} else {
vm.searchResults = response.insureds;
}
});
}
How would I stub the promise so that when I call getInsureds it would resolve the promise and return me the results immediately. I started like this (jasmine test), but I am stuck, as I don't know how to resolve the promise and pass in arguments needed.
it("search returns over 100 results searchResults should contain only 100 records ", function () {
var results103 = new Array();
for (var i = 0; i < 103; i++) {
results103.push(i);
}
var fakeSearchForm = { $valid: true };
var isSearchValidStub = sinon.stub(sut, "isSearchCriteriaValid").returns(true);
var deferred = $q.defer();
var promise = deferred.promise;
var dsStub = sinon.stub(inSearchDataSvc, "getInsureds").returns(promise);
var resolveStub = sinon.stub(deferred, "resolve");
//how do i call resolve and pass in results103
sut.performSearch(fakeSearchForm);
sinon.assert.calledOnce(isSearchValidStub);
sinon.assert.calledOnce(dsStub);
sinon.assert.called(resolveStub);
expect(sut.searchResults.length).toBe(100);
});

At current sinon version v2.3.1, you can use stub.resolves(value) and stub.rejects(value) function
For example, you can stub myClass.myFunction with following code
sinon.stub(myClass, 'myFunction').resolves('the value you want to return');
or
sinon.stub(myClass, 'myFunction').rejects('the error information you want to return');

You just have to resolve the promise before you call the search function. This way your stub will return a resolved promise and then will be called immediately. So instead of
var resolveStub = sinon.stub(deferred, "resolve");
you will resolve the deferred with your fake response data
deferred.resolve({insureds: results103})

Also you can do something like this:
import sinon from 'sinon';
const sandbox = sinon.sandbox.create();
const promiseResolved = () => sandbox.stub().returns(Promise.resolve('resolved'));
const promiseRejected = () => sandbox.stub().returns(Promise.reject('rejected'));
const x = (promise) => {
return promise()
.then((result) => console.log('result', result))
.catch((error) => console.log('error', error))
}
x(promiseResolved); // resolved
x(promiseRejected); // rejected

I had a similar situation and the accepted answer and comments were not working, but along with this question they helped me solve this in the following way. I hope it is helpful for somebody.
var Promise = require('bluebird');
var deferred = Promise.defer();
stub = sinon.stub(deferred, 'resolve').returns(deferred.promise);
deferred.resolve({data: data});
// or
deferred.reject(new Error('fake error'));

There's one more alternative I found. Much pain free than other methods.
You can use this npm package: sinon-stub-promise.
It abstracts much of the details, so that you don't have to invent the wheel again. Helped my write my tests after struggling to simulate a promise for long.
Hope it helps!

Related

Promises structure misunderstood

I have a problem with understanding Promises syntax.
So, what I am trying to do:
getPosts() gets some data from a DB then, I want to get some metadata for each row with another promise call, addMetadata(). Then, once all the metadata is fetched, I want to console it out.
See my attempt below:
var getPosts = function(){
return new Promise(function(resolve, reject){
postModel.find()
.exec(function(err, posts) {
resolve(posts);
);
});
};
var addMetadata = function(posts){
var options = {
host: 'localhost',
port: 3000,
path: '',
method: 'GET'
};
var postPromises = posts.map(function(post) {
return new Promise(function(resolve) {
options.path = '/api/user?id=' + post.profileID;
var req = http.get(options, function(res) {
var bodyChunks = [];
res.on('data', function(chunk) {
bodyChunks.push(chunk);
}).on('end', function() {
var body = Buffer.concat(bodyChunks);
var parsedBody = JSON.parse(body);
post.fullname = parsedBody.user.fullname;
post.profilePic = parsedBody.user.profilePic;
// resolve the promise with the updated post
resolve(post);
});
});
});
});
// Is this the right place to put Promise.all???
Promise.all(postPromises)
.then(function(posts) {
//What should I put here
});
};
getPosts()
.then(function(posts){
return addMetadata(posts);
})
.then(function(posts){//I get a little lost here
console.log();//posts is undefined
});
Of course, my understanding is wrong but I thought I was going the right way. Can someone please guide me to the right direction?
Thanks
Change
// Is this the right place to put Promise.all???
Promise.all(postPromises)
.then(function (posts) {
//What should I put here
});
into
// Is this the right place to put Promise.all???
return Promise.all(postPromises);
This way your addMetadata function will return Promise that resolve when all promises from postPromises resolves or reject if any of postPromises rejects.
The key point to understand the async concept of it and what time the content is available.
Reading this will help to put you in the right direction.
For instance:
var promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise
.then(function(val) {
console.log(val); // 1
return val + 2;
})
.then(function(val) {
console.log(val); // 3
})
After as per your scenario, in order to have all the metadata Promise.all is the way to go.
Promise.all(arrayOfPromises).then(function(arrayOfResults) {
// One result per each promise of AddMetadata
})
What you wanna do here, if I am correct, is called streams, as you wanna call multiple paralel promises as your concept of looping through list of posts using map is not going to work this way
Take a look at this short video introducing streams Streams - FunFunFunction, he is using library for workin with streams called Baconjs
Here is a short example on streams
const stupidNumberStream = {
each: (callback) => {
setTimeout( () => callback(1), 3000 )
setTimeout( () => callback(2), 2000 )
setTimeout( () => callback(3), 1000 )
}
}
stupidNumberStream.each(console.log)
Your getPosts function is good in the sense that its only job is to promisfy the database find. (Though, I think if it's mongo, the exec produces a promise for you).
Your addMetadataToAPost is less good, because it mixes up processing an array of posts and "promisifying" the http.get. Use the same pattern you applied correctly in the first function and return a promise to do a single get and add metadata. (It would be even better to just wrap the get, which you can reuse, and build a simple add-metadata function that returns - rather than creates - a promise)
// renamed pedantically
var addMetadataToAPost = function(post) {
return new Promise(function(resolve) {
options.path = '/api/user?id=' + post.profileID;
var req = http.get(options, function(res) {
var bodyChunks = [];
res.on('data', function(chunk) {
bodyChunks.push(chunk);
}).on('end', function() {
var body = Buffer.concat(bodyChunks);
var parsedBody = JSON.parse(body);
post.fullname = parsedBody.user.fullname;
post.profilePic = parsedBody.user.profilePic;
// resolve the promise with the updated post
resolve(post);
});
});
});
}
Now your batching function is simple:
// also renamed pedantically
var addMetadataToAllPosts = function(posts){
var postPromises = posts.map(function(post) {
return addMetadataToAPost(post)
})
return Promise.all(postPromises)
};
Your original code should work...
getPosts().then(function(posts){
return addMetadataToAllPosts(posts);
})
.then(function(posts){
console.log(posts);//posts should be an array of posts with metadata added
});

How to abort a fetch request?

I've been using the new fetch API instead of the old XMLHttpRequest
It is great but I am missing one crucial function, xhr.abort().
I can't find any information about that functionality for fetch.
Thanks.
UPDATE:
hacky workaround for aborting fetch https://github.com/Morantron/poor-mans-cancelable-fetch
Basically you start the fetch in a web worker and cancel the web worker to abort the fetch
Its still an open issue
All relevant discussion can be found here
https://github.com/whatwg/fetch/issues/447
:(
It's possible to abort fetch via AbortController:
export function cancelableFetch(reqInfo, reqInit) {
var abortController = new AbortController();
var signal = abortController.signal;
var cancel = abortController.abort.bind(abortController);
var wrapResult = function (result) {
if (result instanceof Promise) {
var promise = result;
promise.then = function (onfulfilled, onrejected) {
var nativeThenResult = Object.getPrototypeOf(this).then.call(this, onfulfilled, onrejected);
return wrapResult(nativeThenResult);
}
promise.cancel = cancel;
}
return result;
}
var req = window.fetch(reqInfo, Object.assign({signal: signal}, reqInit));
return wrapResult(req);
}
Usage example:
var req = cancelableFetch("/api/config")
.then(res => res.json())
.catch(err => {
if (err.code === DOMException.ABORT_ERR) {
console.log('Request canceled.')
}
else {
// handle error
}
});
setTimeout(() => req.cancel(), 2000);
Links:
https://developers.google.com/web/updates/2017/09/abortable-fetch
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
I typically use something like this, similar to #ixrock.
// Fetch and return the promise with the abort controller as controller property
function fetchWithController(input, init) {
// create the controller
let controller = new AbortController()
// use the signal to hookup the controller to the fetch request
let signal = controller.signal
// extend arguments
init = Object.assign({signal}, init)
// call the fetch request
let promise = fetch(input, init)
// attach the controller
promise.controller = controller
return promise
}
and then replace a normal fetch with
let promise = fetchWithController('/')
promise.controller.abort()

Returning a value from a function depending on whether a Promise was resolved or not

const dbConnection = require("../dbConnection");
var task = function () {
var response = "";
dbConnection.then(function () {
//do something here
response = "some value";
})
.catch(function () {
response = new Error("could not connect to DB");
});
//I can't return response here because the promise is not yet resolved/rejected.
}
I'm using a node module that someone else wrote. It returns a promise. I want to return either a string or a new Error() depending on whether the Promise object returned by the module resolved or not. How can I do this?
I can't return inside the finally() callback either because that return would apply to the callback function not my task function.
dbConnection.then().catch() will itself return a promise. With that in mind, we can simply write the code as return dbConnection.then() and have the code that uses the function treat the return value as a promise. For example,
var task = function () {
return dbConnection.then(function() {
return "Good thing!"
}).catch(function() {
return new Error("Bad thing.")
})
}
task().then(function(result){
// Operate on the result
}
const dbConnection = require("../dbConnection");
var task = function () {
var response = "";
return dbConnection.then(function () {
//do something here
response = "some value";
return response;
})
.catch(function () {
response = new Error("could not connect to DB");
return response;
});
}
This will return a promise which you can then chain.
The point of using promises is similar to using callbacks. You don't want the CPU to sit there waiting for a response.

NodeJs forEach request-promise wait for all promises before returning

Problem is I'm not able to get the promises to return anything. they.. just come empty.
Every answer I see here on SO is telling me to do just this, though for some reason this is not working. I'm at my wits end, pulling hair and smashing keyboards; Can someone pin-point my dumbness?
var q = require('q');
var request = require('request-promise'); // https://www.npmjs.com/package/request-promise
function findSynonym(searchList) {
var defer = q.defer();
var promises = [];
var url = "http://thesaurus.altervista.org/service.php?word=%word%&language=en_US&output=json&key=awesomekeyisawesome";
var wURL;
searchList.forEach(function(word){
wURL = url.replace('%word%',word);
promises.push(request(wURL));
});
q.all(promises).then(function(data){
console.log('after all->', data); // data is empty
defer.resolve();
});
return defer;
}
var search = ['cookie', 'performance', 'danger'];
findSynonym(search).then(function(supposedDataFromAllPromises) { // TypeError: undefined is not a function [then is not a function]
console.log('->',supposedDataFromAllPromises); // this never happens
});
You're returning the Deferred object defer, which does not have a .then method, instead of the Promise object defer.promise.
But anyway, that's the deferred antipattern, there's no need of using deferreds here. Just return the promise that Promise.all gets you:
function findSynonym(searchList) {
var url = "http://thesaurus.altervista.org/service.php?word=%word%&language=en_US&output=json&key=awesomekeyisawesome";
var promises = searchList.map(function(word) {
return request(url.replace('%word%', word));
});
return q.all(promises).then(function(data){
console.log('after all->', data); // data is empty
return undefined; // that's what you were resolve()ing with
});
}
So, turns out I was resolving the promises or something something. returning the q.all() worked pretty well :)
function findSynonym(searchList) {
var promises = [];
var url = "http://thesaurus.altervista.org/service.php?word=%word%&language=en_US&output=json&key=REDACTED";
var wURL;
searchList.forEach(function(word){
wURL = url.replace('%word%',word);
promises.push(request({url:wURL}));
});
return q.all(promises);
}
var search = ['cookie', 'performance', 'danger'];
findSynonym(search)
.then(function(a){
console.log('->',a);
});

AngularJS Promise Returns Empty Array

I have this code in a factory:
getAyahsByJuz: function (juzIndex) {
var response = [];
var promises = [];
var self = this;
var deferred = $q.defer();
$timeout(function () {
$http.get('data/quran.json').success(function (data) {
var ayahs = Quran.ayah.listFromJuz(juzIndex);
angular.forEach(ayahs, function (value, key) {
var promise = self.getVerse(value.surah, value.ayah).then(function (res) {
var verse = {
surah: value.surah,
ayah: value.ayah,
text: res
};
response.push(verse);
}, function (err) {
console.log(err);
});
promises.push(promise);
});
});
}, 30);
$q.all(promises).then(function() {
deferred.resolve(response);
});
return deferred.promise;
},
Please note that everything is working fine the verse object is returning properly. However, when I use this in a controller using .then(res). res returns [] instead of the array filled with the verse objects.
Can anyone point out why? Thanks!
The short answer is because your $q.all runs before $timeout & before the $http embedded in $timeout. Let's boil your original code down to its relevant components:
getAyahsByJuz: function (juzIndex) {
var response = [];
var promises = [];
var deferred = $q.defer();
// ...irrelevant stuff that will happen after a $timeout
// this happens IMMEDIATELY (before $timeout):
$q.all(promises).then(function() { // wait for empty promise array
deferred.resolve(response); // resolve with empty response array
}); // side note: this is a broken chain! deferred.promise can't reject
return deferred.promise; // send promise for empty array
}
See the problem? If for some odd reason you need to keep that $timeout, here's the fix with substantial promise refactoring & removing the awful jquery-inspired non-promisy success syntax):
getAyahsByJuz: function (juzIndex) {
var self = this;
// $timeout itself returns a promise which we can post-process using its callback return value
return $timeout(function () {
// returning the $http promise modifies the $timeout promise
return $http.get('data/quran.json').then(function (response) { // you never used this response!
var versePromises = [];
var ayahs = Quran.ayah.listFromJuz(juzIndex);
angular.forEach(ayahs, function (value, key) {
// we'll push all versePromises into an array…
var versePromise = self.getVerse(value.surah, value.ayah).then(function (res) {
// the return value of this `then` modifies `versePromise`
return {
surah: value.surah,
ayah: value.ayah,
text: res
};
});
versePromises.push(versePromise);
});
return $q.all(versePromises); // modifies $http promise — this is our ultimate promised value
// if a versePromise fails, $q.all will fail; add a `catch` when using getAyahsByJuz!
});
}, 30);
}
However, there is still a huge issue here… why aren't you using the server response of your $http call anywhere? What is the point of that first call?
Also I find that $timeout to be extremely suspicious. If you need it then it's likely there's something bad going on elsewhere in the code.

Categories

Resources