Jest doesn't wait for async beforeAll to finish - asynchronous

Im trying to test getting all users from my REST API.
describe('GET', () => {
let userId;
// Setup create the mock user
beforeAll(async () => {
//Create the user
return await request
.post(routes.users.create)
.set('Accept', 'application/json')
.send(TEST_USER_DATA)
.then(res => userId = res.body.id)
})
// Clean up, deleting all the fake data that we created for this test suite
afterAll(async () => {
// Clean up, delete the user we created
return await request.delete(routes.users.delete(userId));
})
it('should get all users', async () => {
const usersResponse = await request
.get(routes.users.getAll)
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /json/);
// Logs an empty array
console.log(usersResponse.body);
expect(usersResponse.status).to.equal(200);
expect(Array.isArray(usersResponse.body)).to.be.true();
});
});
But it look as though my it() block doesn't wait for beforeAll() to finish, because userResponse.body() is just an empty array. But when I do the same think in Postman(e.g. Create a mock user, then get all users, it displays an array with the user that we created) so the problem is definitely not in the server-side.
I've already tried writing my beforeAll block like that:
beforeAll(async () => {
//Create the user
return await new Promise((resolve) => {
request
.post(routes.users.create)
.set('Accept', 'application/json')
.send(TEST_USER_DATA)
.then(res => userId = res.body.id)
.then(() => resolve)
})
})
And like that:
beforeAll(async (done) => {
//Create the user
request
.post(routes.users.create)
.set('Accept', 'application/json')
.send(TEST_USER_DATA)
.then(res => userId = res.body.id)
.then(() => done());
})
But neither of them worked.
EDIT
As #jonrsharpe suggested I changed my beforeAll a bit to check the response status, and that we actually created a user
beforeAll(async () => {
//Create the user
return await request
.post(routes.users.create)
.set('Accept', 'application/json')
.send(TEST_USER_DATA)
.expect(200)
.then(res => {
userId = res.body.id;
// Log the correct user
console.log(res.body);
})
})
And the beforeAll block doesn't fail, so the creation of a user by itself is working fine.

Related

fetch res.json() working with await and async but not working with promises

I have always been made to believe that async and await and promises were one and the same thing, here, in my code, res.json() is working with await but isnt working at all with .then(). maybe I am missing something. please this is not a duplicate question. I have been scouring the internet for answers for the most part of two days now.
Here is the code with .then, aync and await, and i will also include the code from my server that sends the json
CODE USING .then()
const requestURL = "/auth/profile";
const request = new Request(requestURL, {
method: 'POST'
});
fetch(request)
.then( (res) => {
res.json()
})
.then(obj => {
console.log(obj)
})
.catch(err => {
console.log(err)
})
here, console.log(obj) logs undefined
CODE USING ASYNC AND AWAIT
const requestURL = "/auth/profile";
const request = new Request(requestURL, {
method: 'POST'
});
fetch(request)
.then( aync (res) => {
const obj = await res.json()
console.log(obj)
})
.catch(err => {
console.log(err)
})
here, console.log(obj) logs the correct json object as expected
server request handler (nodejs)
const getProfile = async (req, res, next) => {
const result = JSON.stringify({"status":"not logged in", "body": "there is a big poblem"})
res.setHeader('content-type', 'application/json');
res.status(200).send(result)
}
While I have used other tools to make sure I have the response from my server properly set up, if i am missing something kindly indicate to me, cheers
The problem is in your handling of of the chains. You need to return data to the next then, it's not automatic and varies depending on scope. Change to:
fetch(request)
.then( (res) => {
// Process.. then return some data to the next chain in line.
return res.json()
})
.then(obj => {
// Now, obj will be what the line above 'returned'
console.log(obj)
})
.catch(err => {
console.log(err)
})

CloudFunctions: Request is sending twice

I have an issue and I need help since I'm learning.
I have a flutter app that saves on Firebase/Firestore data, when a user requests a friendship I add it on both sender and target user, changing the IDs of sendTo and sentBy.
My problem is that CloudFunctions detect well that 2 collections from different users have been changed and notify me 2x (target user). So code is fine but should only notify once/target user
I'm using FCM to send local notifications.
exports.sendRequestNotification = functions.firestore
.document('users/{userId}/requests/{requestId}')
.onCreate((snap, context) => {
const docReq = snap.data()
/*console.log(docReq)*/
const sentBy = docReq.sentBy
const sentTo = docReq.sentTo
const contentRequest = docReq.code
if(contentRequest !== null){
// Get push token user to (receive)
admin
.firestore()
.collection('users')
.where('userId', '==', sentTo)
.get()
.then(querySnapshot => {
querySnapshot.forEach(userTo => {
/*console.log(`Found request user to: ${userTo.data().userId}`)*/
if (userTo.data().pushToken) {
// Get info user from (sent)
admin
.firestore()
.collection('users')
.where('userId', '==', sentBy)
.get()
.then(querySnapshot2 => {
querySnapshot2.forEach(userFrom => {
/*console.log(`Found request user from: ${userFrom.data().userId}`)*/
const payload = {
notification: {
title: `${userFrom.data().nickname}`,
body: contentRequest,
badge: '1',
sound: 'default'
}
}
// Let push to the target device
admin
.messaging()
.sendToDevice(userTo.data().pushToken, payload)
.then(response => {
/*console.log('Successfully sent request:', response)*/
})
.catch(error => {
console.log('Error sending request:', error)
})
})
})
} else {
console.log('User request or token not found')
}
})
})
return null
}
})
It is not very clear from your code why it would send the notification twice (since you check that userTo.data().userId !== sentBy). But what is sure is that you are not returning a Promise that resolves when all the asynchronous operations (get() and sendToDevice()) are completed.
I would suggest you watch the official Video Series (https://firebase.google.com/docs/functions/video-series/) which explain very well this point about returning Promises for background functions (in particular the ones titled "Learn JavaScript Promises").
In particular, you will see in the videos that if you don't return a Promise, the Cloud Function may terminate before asynchronous operations are completed, potentially resulting in some inconsistent (not logical) results .
So, you should give a try with the following adapted code, which returns the promises chain:
exports.sendRequestNotification = functions.firestore
.document('users/{userId}/requests/{requestId}')
.onCreate((snap, context) => {
const db = admin.firestore();
const docReq = snap.data();
/*console.log(docReq)*/
const sentBy = docReq.sentBy;
const sentTo = docReq.sentTo;
// Get push token user to (receive)
return db.collection('users')
.where('userId', '==', sentTo)
.get()
.then(querySnapshot => {
//We know there is only one document (i.e. one user with this Id), so lets use the docs property
//See https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot.html#docs
const userTo = querySnapshot.docs[0];
if (userTo.data().pushToken && userTo.data().userId !== sentBy) {
// Get info user from (sent)
return db.collection('users')
.where('userId', '==', sentBy)
.get();
} else {
console.log('User request or token not found')
throw new Error('User request or token not found');
}
})
.then(querySnapshot => {
const userFrom = querySnapshot.docs[0];
const payload = {
notification: {
title: `${userFrom.data().nickname}`,
body: `requestNotify`,
badge: '1',
sound: 'default'
}
}
return admin
.messaging()
.sendToDevice(userTo.data().pushToken, payload);
})
.catch(error => {
console.log('Error:', error);
return false;
})
})

Can't access data base from a Firebase function

I tried everything , I have this cloud function (that otherwise works) :
exports.contentServer = functions.https.onRequest((request, response) => {
admin.database().ref('/list/' + "abc").once('value').then(function(snapshot) {
console.log(snapshot.val() );
return null;
}).catch(function(error) {
console.log("Error getting document:", error);
return response.send(error);
});
});
or also this :
admin.database().ref('/list').once('value').then(function(snapshot) {
var event = snapshot.val();
app.tell('Result: '+event);
});
and this :
exports.contentServer = functions.https.onRequest((request, response) => {
var db = admin.database();
db.ref("list/abc").once("value").then(snap => {
var store = snap.val().description;
return store;
}).then(() => {
var store = snap.val().description;
return store;
}).then(snap => {
var store = snap.val().description;
return store;
}).catch(err => {
console.log(err);
response.send("error occurred");
});
});
and always get back the error :
"Could not handle the request"
Or I get error on deploy that :
Each then() should return a value or throw
I have a collection called list, inside I have a document named "abc".
Is there something I have to include ? something I have to setup in Firebase to make it work ? anything basic nobody write on the docs ?
Modified following the comments above explaining the OP uses Firestore and not the Realtime Database
You should do as follows. You have to wait that the promise returned by the get() method resolves before sending back the response. For this you need to use the then() method, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
exports.contentServer = functions.https.onRequest((request, response) => {
admin.firestore().collection('list').doc('abc').get()
.then(docSnapshot => {
console.log(docSnapshot.data());
return response.send(docSnapshot.data()); // or any other value, like return response.send( {result: "success"} );
})
.catch(error => {
console.log("Error getting document:", error);
return response.status(500).send(error);
});
});
As written in the comments above, I would suggest that you watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/
Try this
Updated. Return the response inside then() as what #Renaud Tarnec pointed out.
Using realtime database
exports.contentServer = functions.https.onRequest((request, response) => {
var database = admin.database();
database.ref('list').child('abc').once("value", snapshot => {
const data = snapshot.val();
return response.send(data);
}).catch(error => {
return response.status(500).send(error);
});
});
If you are using firestore.
exports.contentServer = functions.https.onRequest((request, response) => {
const firestore = admin.firestore();
firestore.collection("list").doc('abc').get().then(doc => {
console.log(doc.data());
return response.send(doc.data());
}).catch(error => {
return response.status(500).send(error);
});
});
Important: Don't forget to terminate the request by calling response.redirect(), response.send(), or responses.end() so you can avoid excessive charges from functions that run for too long

What is the suitable query for the following case?

This is my database structure:
I am trying to list all users with "locale" equal to "Cairo, Egypt" so I made the following query:
exports.calculateMatches = functions.https.onRequest((request, response) => {
// Access users' profiles that are located in the locale of the requesting user
databaseRef.child("users").orderByChild("locale").equalTo(request.query.locale).once("value")
.then(snap => {
snap.forEach(profile => {
console.log(profile);
});
});
});
Note this function is deployed to firebase cloud functions and this is what I get in the logs:
HTTPS type functions require that you send a response to the client in order to terminate the function. Without that, they will always time out, and the client will be waiting the whole time.
For example:
const databaseRef = admin.database().ref('')
exports.calculateMatches = functions.https.onRequest((request, response) => {
databaseRef.child("users").orderByChild("locale").equalTo(request.query.locale).once("value")
.then(snap => {
const profiles = []
snap.forEach(profile => {
profiles.push(profile.val())
});
response.send(profiles)
})
.catch(error => {
response.status(500).send(error)
});
});

Why does redux-mock-store don't show an action dispatched in catch promises?

I'm very bad when it comes to thinking of a title question, sorry for that.
My Problem:
I'm unit testing my async redux actions like it's suggested in the docs. I mock the API calls with nock and check for the dispatched actions with redux-mock-store. It works great so far, but I have one test that fails even though it clearly does work. The dispatched action neither does show up in the array returned by store.getActions() nor is the state changed in store.getState(). I'm sure that it does happen because I can see it when I test manually and observe it with Redux Dev Tools.
The only thing that is different in this action dispatch is that it is called in a promise in a catch of another promise. (I know that sounds confusing, just look at the code!)
What my code looks like:
The action:
export const login = (email, password) => {
return dispatch => {
dispatch(requestSession());
return httpPost(sessionUrl, {
session: {
email,
password
}
})
.then(data => {
dispatch(setUser(data.user));
dispatch(push('/admin'));
})
.catch(error => {
error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
};
}
This httpPost method is just a wrapper around fetch that throws if the status code is not in the 200-299 range and already parses the json to an object if it doesn't fail. I can add it here if it seems relevant, but I don't want to make this longer then it already is.
The action that doesn't show up is dispatch(setError(data.error)).
The test:
it('should create a SET_SESSION_ERROR action', () => {
nock(/example\.com/)
.post(sessionPath, {
session: {
email: fakeUser.email,
password: ''
}
})
.reply(422, {
error: "Invalid email or password"
})
const store = mockStore({
session: {
isFetching: false,
user: null,
error: null
}
});
return store.dispatch(actions.login(
fakeUser.email,
""))
.then(() => {
expect(store.getActions()).toInclude({
type: 'SET_SESSION_ERROR',
error: 'Invalid email or password'
})
})
});
Thanks for even reading.
Edit:
The setErroraction:
const setError = (error) => ({
type: 'SET_SESSION_ERROR',
error,
});
The httpPostmethod:
export const httpPost = (url, data) => (
fetch(url, {
method: 'POST',
headers: createHeaders(),
body: JSON.stringify(data),
})
.then(checkStatus)
.then(response => response.json())
);
const checkStatus = (response) => {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
};
Because of you are using nested async function in catch method - you need to return the promise:
.catch(error => {
return error.response.json()
.then(data => {
dispatch(setError(data.error))
})
});
Otherwise, dispatch will be called after your assertion.
See primitive examples:
https://jsfiddle.net/d5fynntw/ - Without returning
https://jsfiddle.net/9b1z73xs/ - With returning

Resources