Testing promises within promises - javascript

I used to have the following code:
function makeCall(userInfo) {
api.postUser(userInfo).then(response => {
utils.redirect(response.url);
})
// other logic
return somethingElse;
}
And I was able to write a test that looked like this:
const successPromise = Promise.resolve({ url: 'successUrl' })
beforeEach(function() {
sinon.stub(api.postUser).returns(successPromise);
}
afterEach(function() {
api.postUser.restore();
}
it "calls API properly and redirects" do
makeCall({});
expect(api.postUser).calledWith(userInfo).toBe(true);
successPromise.then(() => {
expect(utils.redirect.calledWith('successUrl')).toBe(true);
done();
}
emd
And everything was green.
Now, I had to add another promise to make another external call, before doing the api postUser call, so my code looks like this:
function makeCall(names) {
fetchUserData(names).then(userData => {
return api.postUser(userData).then(response => {
utils.redirect(response.url);
})
})
// other logic
return somethingElse;
}
where fetchUseData is a chain of many promises, such like:
function fetchNames(names) {
// some name regions
return Promise.all(names);
}
function fetchUserData(names) {
fetchUsersByNames(names).then(users => {
// For now we just choose first user
{
id: users[0].id,
name: users[0].name,
}
});
}
And the tests I had fail. I am trying to understand how to change my tests to make sure that I am still testing that I do the final API call properly and the redirect is also done. I want to stub what fetchUserData(names), to prevent doing that HTTP call.

You're not using promises correctly. Your code doesn't have a single return statement, when it should have several (or it should be using arrow functions in such a way that you don't need them, which you're not doing).
Fix your code:
function makeCall(names) {
// v---- return
return fetchUserData(names).then(userData => {
// v---- return
return api.postUser(userData).then(response => {
utils.redirect(response.url);
})
})
}
function fetchUserData(names) {
// v---- return
return fetchUsersByNames(names).then(users => {
// For now we just choose first user
// v---- return
return {
id: users[0].id,
name: users[0].name,
}
});
}
Once you've done that, you can have your test wait for all of the operations to finish.
Test code:
makeCall(['name']).then(() =>
expect(api.postUser).calledWith(userInfo).toBe(true);
expect(utils.redirect.calledWith('successUrl')).toBe(true);
done();
});

You should add a return statement, otherwise you are not returning promises nowhere:
function fetchNames(names) {
// some name regions
return Promise.all(names);
}
function fetchUserData(names) {
return fetchUsersByNames(names).then(users => {
// For now we just choose first user
{
id: users[0].id,
name: users[0].name,
}
});
}
So when you are using Promise.all(), then you will have as result of the promise an array with all the value returned by all the promises.
So then this method should look like this when called:
fetchNames(names).then((arrayOfResolvedPromises) => {
// here you will have all your promised resolved and the array holds all the results
});
So inside your test you can move your done inside the block where all the promises will be resolved.
In addition, I strongly suggest you to use a library as chai-as-promised for testing promises.
It has a lot of nice methods for testing your promises.
https://github.com/domenic/chai-as-promised

Related

How to search an array of objects obtained by axios for an id? Vue 2

I am trying to verify if the user is inside that list that I capture by axios, the issue is that I have used the FILTER option but it always returns undefined or [], being that if the user exists in that array.
I can't think what else to do, because I validate if it is by console.log() the variable with which I ask and if it brings data.
created() {
this.getStagesDefault()
this.getSalesman()
this.getStagesAmountByUser()
},
methods: {
async getSalesman(){
const { data } = await axios.get('salesman')
this.employees = data.data
},
getStagesAmountByUser(){
console.log(this.user['id'])
var objectUser = this.employees.filter(elem => {
return elem.id === this.user['id']
})
console.log(objectUser)
},
Console
Vue data
The method getSalesman is asynchronous, meaning that getStagesAmountByUser will start executing before getSalesman finishes.
Two ways to fix the problem:
Await the getSalesman method, but you have to make the created method async as well. Change the code as follows:
async created() {
this.getStagesDefault()
await this.getSalesman()
this.getStagesAmountByUser()
}
Attach a .then to the getSalesman function, and start the next one inside the .then. Change the code as follows:
created() {
this.getStagesDefault()
this.getSalesman().then(() => this.getStagesAmountByUser())
}
getSalesman is an async method. At the time of the filter, the array being filtered is still empty.
this.getSalesman() // this runs later
this.getStagesAmountByUser() // this runs right away
Have the methods run sequentially by awaiting the async method:
await this.getSalesman()
this.getStagesAmountByUser()
You can avoid the inefficient clientside filtering if you pass the id to the backend and only select by that id.
Additionally, created only gets called once unless you destroy the component which is also inefficient, so watch when user.id changes then call your method again.
Plus don't forget you must wrap any async code in a try/catch else you will get uncaught errors when a user/salesman is not found etc, you can replace console.error then with something which tells the user the error.
{
data: () => ({
employee: {}
}),
watch: {
'user.id' (v) {
if (v) this.getEmployee()
}
},
created() {
this.getEmployee()
},
methods: {
getEmployee() {
if (typeof this.user.id === 'undefined') return
try {
const {
data
} = await axios.get(`salesman/${this.user.id}`)
this.employee = data.data
} catch (e) {
console.error(e)
}
}
}
}

How to get asynchronous data from an object's `get()` without returning a Promise

In NodeJS, I have an object like,
var scope = { word: "init" };
Using Object.defineProperty as described in MDN I re-write the get() function to be like this,
Object.defineProperty(scope, 'word', {
get: function() {
return Math.random();
}
});
Which correctly returns a new random each time I scope.word in the console. However the function must also get data from a function with a callback. So it works pretty much like a setTimeout,
Object.defineProperty(scope, 'word', {
get: function() {
setTimeout(() => {
return Math.random();
}, 1000)
}
});
Now everytime I do scope.word I get,
undefined
Because the get() function is synchronous. This can be of course solved by returning a Promise,
Object.defineProperty(scope, 'word', {
get: function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(Math.random());
}, 1000)
});
}
});
But then I would need to do scope.word.then(...) but the whole idea behind what we are building is that the developer only has to scope.word like if it was a plain easy-to-use variable. Like an Angular's $scope or a VUE.js 'data'.
How can I make the get() function return an actual value, not a Promise? Is it possible to workaround using async / await? How?
One of the solutions is to pass the callback function like this.
const scope = {}
Object.defineProperty(scope, 'word', {
value: (cb)=>{
setTimeout(() => {
cb(Math.random())
}, 1000)
}
});
scope.word(res=>console.log(res))

How can I use .then() on an Array?`

I'm doing a lot of promise based operations in an Express/Sequelize app. In order to control the data flow, I want to follow the promise model as closely as possible.
Here's a snippet of what I'm currently doing:
AreaList.forEach( area => {
distances.push({
target: tgt,
source: area,
distance: areaController.calcDist(tgt, area),
country: areaController.determineCountry(area)
}).then(() => { //this is where I would like to have the .then() function, but get the error.
distances.sort((a1, a2) =>{
return a1.distance - a2.distance;
}).filter(area => area.country === country)
.forEach(area => {
Insider.findAll({
where: {
areaCode: area.source,
country: area.country,
language: language,
gender: gender
}
}).then( insider => {
returnUsers.push(insiders);
}).then(_ => {
returnUsers = returnUsers.splice(0,10);
res.status(200).send(returnUsers);
});
});
});
});
How can I either provide the .then() for the Array, or simulate the .then()?
The problem you are having is mixing synchronous code with asynchronous code. In your code snippet above you have various pieces of synchronous code. AreaList.forEach, distances.push, distances.sort are all synchronous operations.
What it seems to me you are trying to do is process some code while pushing it into an array, this may or may not be asynchronous (areaController.calcDist(tgt, area)).
I would rewrite something like this assuming areaController.calcDist(tgt, area) is a synchronous operation:
let distances = AreaList.map(area => {
return {
target: tgt,
source: area,
distance: areaController.calcDist(tgt, area),
country: areaController.determineCountry(area)
};
})
.sort((a1, a2) =>{
return a1.distance - a2.distance;
})
.filter(area => area.country === country);
let findUsers = distances.map(area => {
return Insider.findAll({
where: {
areaCode: area.source,
country: area.country,
language: language,
gender: gender
}
});
});
Promise.all(findUsers).then(users => {
let returnUsers = users.splice(0, 10);
res.status(200).send(returnUsers);
})
.catch(err => {
//handle errors
});
then() is a function provided by javascript promises resolve. if you want to do a sequence of stuff when going through each item in your AreaList, you should do this:
pushDistances(distance) {
return new Promise((resolve) => {
distance.push(distance);
resolve();
});
with the resolve() function, you can either just resolve() it without anything or attach something that you'd like to resolve with: like the distance you just pushed or the updated distances array with the newly pushed distance.
resolve(distances) or resolve(distance);
and then in your .then(), you get either distances or distance, based on what you resolved with. like this:
pushDistances().then((distance or distances) => {
within this function you have access to distance or distances: based on what you resolved with.
});
and then you can just chain a bunch of functions that return promises
pushDistances().then(() => anotherFunctionThatReturnsPromise()).then(() => {})
that's a general overview of how promises work. You should look more into
Javascript Promises to see how you can chain promises.

Binding variable on redux dispatch ActionCreator

I have an array of promises, and I'm trying to push new promises into that array inside of another dispatch.then function, but it appears that the array is always out of scope
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params
let promises = []
versions.forEach((id) => {
// get the version info, and then pass it along
dispatch(ActionCreators.version({ id: id })).bind(promises).then((version) => {
promises.push(dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
})))
})
})
//
promises.push(dispatch(ActionCreators.configuration()))
promises.push(dispatch(ActionCreators.byRef({ language_tag })))
console.log(promises.length)
return Promise.all(promises)
}
},
I've tried a few different approaches, such as setting var that = this right before the dispatch inside of the versions loop, and what is shown here, trying to use .bind(promises) on the dispatch.
promises.length is always 2, (because of the two that are actually getting pushed at the bottom). I can console statements inside of the .then so I know it's getting executed, but the dispatches are not ending up in the promises array.
I could very well be thinking of the dispatch function in an incorrect way.
Any help would be appreciated!
The problem is that since you're adding the promises on then(), you have already returned the array by the time you're adding the promises. So they do get added, but too late.
Instead, try this:
load(params, auth) {
return dispatch => {
const { passage, versions, language_tag } = params;
let promises = [];
versions.forEach((id) => {
// get the version info, and then pass it along
promises.push(dispatch(ActionCreators.version({ id: id })).then((version) => {
return dispatch(ActionCreators.passages({
id: id,
references: [passage],
versionInfo: {
local_abbreviation: version.abbreviation,
local_title: version.title,
id: version.id,
},
}));
}));
});
//
promises.push(dispatch(ActionCreators.configuration()));
promises.push(dispatch(ActionCreators.byRef({ language_tag })));
console.log(promises.length);
return Promise.all(promises)
}
}

Method chaining in Javascript in Angular app

in my Angular web app, I have a module called ApplicationModule.
ApplicationModule has get and set functions.
In one of my controllers, I call function like below:
ApplicationModule.get().then(function (response) {
//do something with response
});
GET() function returns an object called application. With the returned object, I would like to do something with it. so I use then to chain the method but I get an error saying angular.js:13424 TypeError: Cannot read property 'then' of undefined.
Updated what get() is.
ApplicationModule.get = function () {
if (!localStorage.application) {
Restangular.all('session').one('user').get().then(function (response) {
application = {
"id": response.current_application_id,
"user_id": response.id,
"visa_type": response.current_type
}
localStorage.setItem("application", JSON.stringify(application));
return application
});
} else {
return JSON.parse(localStorage.application)
}
}
What am I doing wrong here?
Your method does neither return the application nor does it return an object at all if localStorage.application is falsy.
You are doing something asynchronous in your function, so you need to always return a promise:
ApplicationModule.get = function () {
if (!localStorage.application) {
return Rectangular.all('session').one('user').get().then(function (response) {
// ^^^^^^ returning the promise
var application = {
"id": response.current_application_id,
"user_id": response.id,
"visa_type": response.current_type
};
localStorage.setItem("application", JSON.stringify(application));
return application;
// ^^^^^^ returning the value that the promise will resolve with
});
} else {
return $q.resolve(JSON.parse(localStorage.application));
// ^^^^^^^^^^ creating a promise here as well for consistent interface
}
}
The method .then() is only applicable to promises. In order to do something with your application object I suggest storing the value as a variable.
var application = ApplicationModule.get();
foo(application);

Categories