I am trying to test a console.error output when a promise rejects using Jest. I am finding that the promise seems to be resolving after my test has run, causing the test to fail.
Example Function:
export default function doSomething({ getData }) {
const success = data => {
//do stuff with the data
}
const handleError = () => {
//handle the error
}
getData.then(response => success(response)).catch(error => {
console.error(error)
handleError()
})
}
Example Test File:
import doSomething from "doSomething"
it("should log console.error if the promise is rejected", async () => {
const getData = new Promise((resolve, reject) => {
reject("fail");
});
global.console.error = jest.fn();
await doSomething({ getData });
expect(global.console.error).toHaveBeenCalledWith("fail");
})
//fails with global.console.error has not been called
When I was exploring the problem, I noticed that if I add in a console.log and await that, it works.
This will pass...
import doSomething from "doSomething"
it("should log console.error if the promise is rejected", async () => {
const getData = new Promise((resolve, reject) => {
reject("fail");
});
global.console.error = jest.fn();
await doSomething({ getData });
await console.log("anything here");
expect(global.console.error).toHaveBeenCalledWith("fail");
})
How do I test for this correctly? Should I refactor how my getData function is being called? It needs to get called as soon as the doSomething function is called.
Why is the original test failing?
The trick for understanding why the first test example won't pass is in digging into what the await operator is actually doing. From the Mozilla docs:
[rv] = await expression;
expression - A Promise or any value to wait for.
rv - Returns the fulfilled value of the promise, or the value itself if it's not a Promise.
In your first test the value of expression is the return value of the doSomething function. You aren't returning anything from this function, so the return value will be undefined. This isn't a Promise, so nothing for await to do, it will just return undefined and move on. The expect statement will then fail, as you haven't actually awaited the inner promise: getData.then(...).catch(...).
To fix the test, without adding in the extra line, await console.log("anything here");, simply return the inner promise from the doSomething function, so that the await operator will actually operate on the Promise.
export default function doSomething({ getData }) {
return getData.then(...).catch(...);
...
}
Is this the right way to test this?
I don't think that there is anything majorly wrong with how the doSomething function is written. This kind of dependency injecting usually makes functions easier to test than trying to mock out the inner workings of a function.
I would only recognise though that because you are injecting a Promise (getData), and resolving it within the function, you have made the doSomething function asynchronous (which is what made it more complicated to test).
Had you resolved the Promise and then called doSomething on the value it resolves to, getData.then(doSomething).catch(handleError), your doSomething function would have been synchronous and easier to test. I would also say that writing it this way makes it much more verbose that something asynchronous is going on, whereas the original, doSomething({ getData }), hides that within the doSomething function body.
So nothing strictly incorrect, but maybe a few things to think about that might make testing easier and code more verbose. I hope that helps!
Related
this is a problem that is going around for DAYS in my team:
We can't figure out how to save the result of a promise (after .then) in a variable.
Code will explain better what I mean:
We start with a simple async function that retrieves the first item of a list:
const helloWorld = () => [{ name: 'hi' }];
async function getFirstHelloWorld() {
const helloWorldList = await helloWorld();
return helloWorldList[0].name;
}
Now, we would like to call this function and save the content of aList:
let aList;
const a = async() => {
aList = await getFirstHelloWorld()
}
a().then(() => {
console.log('got result:', aList)
console.log('aList is promise:', aList instanceof Promise)
});
console.log(aList)
aList called within the .then(), console.logs the right value.
aList called outside the .then(), console.logs Promise { }
How can we save returned values from promises to a variable?
You cannot take an asynchronously retrieved value, stuff it in a higher scoped variable and then try to use it synchronously. The value will not be present yet because the asynchronous result has not been retrieved yet. You're attempting to use the value in the variable before it has been set.
Please remember that await only suspends execution of a local function, it does not stop the caller from running. At the point you do an await, that function is suspended and the async function immediately returns a promise. So, NO amount of await or return gets you anything by a promise out of an async function. The actual value is NEVER returned directly. All async functions return a promise. The caller of that function then must use await or .then() on that promise to get the value.
Try running this code and pay detailed attention to the order of the log statements.
console.log("1");
const helloWorld = () => [{ name: 'hi' }];
async function getFirstHelloWorld() {
const helloWorldList = await helloWorld();
return helloWorldList[0].name;
}
let aList;
const a = async() => {
console.log("beginning of a()");
aList = await getFirstHelloWorld()
console.log("end of a()");
}
console.log("2");
a().then(() => {
console.log('got result:', aList)
console.log('aList is promise:', aList instanceof Promise)
});
console.log("3");
console.log('attempting to use aList value');
console.log(aList)
console.log("4");
That will give you this output:
1
2
beginning of a()
3
attempting to use aList value
undefined
4
end of a()
got result: hi
aList is promise: false
Here you will notice that you are attempting to use the value of aList BEFORE a() has finished running and set the value. You simply can't do that in Javascript asynchronous code, whether you use await or .then().
And, remember that the return value from an async function becomes the resolved value of the promise that all async functions return. The value is not returned directly - even though the code syntax looks that way - that's a unique property of the way async functions work.
Instead, you MUST use the asynchronous value inside the .then() where you know the value is available or immediately after the await where you know the value is available. If you want to return it back to a caller, then you can return it from an async function, but the caller will have to use .then() or await to get the value out of the promise returned from the async function.
Assigning an asynchronously retrieved value to a higher scoped variable is nearly always a programming error in Javascript because nobody wanting to use that higher scoped variable will have any idea when the value is actually valid. Only code within the promise chain knows when the value is actually there.
Here are some other references on the topic:
Why do I need to await an async function when it is not supposedly returning a Promise?
Will async/await block a thread node.js
How to wait for a JavaScript Promise to resolve before resuming function?
Using resolved promise data synchronously
How to Write Your Code
So, hopefully it is clear that you cannot escape an asynchronous result. It can only be used in asynchronous-aware code. You cannot turn an asynchronously retrieved result into something you can use synchronously and you usually should NOT be stuffing an asynchronous result into a higher scoped variable because that will tempt people writing code in this module to attempt to use the variable BEFORE is is available. So, as such, you have to use the value inside a .then() handler who's resolved value has the value you want or after an await in the same function where the await is. No amount of nesting in more async functions let you escape this!
I've heard some people refer to this as asynchronous poison. Any asynchronous operation/value anywhere in a flow of code makes the entire thing asynchronous.
Here's a simplified version of your code that shows returning the value from an async function which will make it the resolved value of the promise the async function returns and then shows using .then() on that promise to get access to the actual value. It also shows using .catch() to catch any errors (which you shouldn't forget).
FYI, since this is not real asynchronous code (it's just synchronous stuff you've wrapped in some promises), it's hard to tell what the real end code should be. We would need to see where the actual asynchronous operation is rather than just this test code.
const helloWorld = () => [{ name: 'hi' }];
async function getFirstHelloWorld() {
const helloWorldList = await helloWorld();
return helloWorldList[0].name;
}
getFirstHelloWorld().then(name => {
// use the name value here
console.log(name);
}).catch(err => {
console.log(err);
});
You need to return the variable like this and use it within the .then callback.
const a = async() => {
const res = await getFirstHelloWorld()
return res
}
a().then((data) => {
console.log('got result:', data)
});
I have a async function which returns a promise. I'm confused to how I access the Promise value. I came across .then(). It works with .then(), but I want to make the promise value global. Which isn't possible if i use .then().
Here is my code:
async function getUser() {
try {
response = await axios.post('/',data);
}
catch (error) {
console.error(error);
}
return response.data;
}
var global = getUser();
console.log(global);
This code here returns a promise like so:
I'm wondering how can I access the Promise value (which we can see in the image) and make it global? I'm new to JavaScript unable to wrap my head around async functions.
You can't. Using async means your function actually wraps a promise around it. So global in var global = getUser() is a promise. You can either await in in an async function or use .then().
If you want the code to look synchronous, you could have a main function, and just call main 'globally':
async function main() {
// because main is an async function, it can unwrap the promise
const user = await getUser();
console.log({user});
}
main();
You should not try to use user 'globally', and only use it in main. This is because the promise of getUser might not even be done yet, so it would be unresolved promise. You should only use it when you explicitly waited (await or then'd) the promise. Also, global variables are not great in any programming language, including javascript.
You can set your global value like so:
var global;
getUser().then(user => global = user);
function getUser() {
return Promise.resolve("User #1");
}
var global;
getUser().then(user => global = user);
console.log("1", global);
setTimeout(() => console.log("2", global), 500);
However keep in mind that other synchronous code has no idea when the global variable is set. The value will be undefined until the callback is called. The better option might be what you're already doing:
var global = getUser();
This way other synchronous code can attach callbacks by doing:
global.then(user => {
// ...
});
function getUser() {
return Promise.resolve("User #1");
}
var global = getUser();
global.then(user => console.log("1", user));
setTimeout(() => {
global.then(user => console.log("2", user));
}, 500);
I have a Vuex store and I am trying to fetch data from the Firebase Realtime Database. I am initially fetching the user information, however afterwards I would like to fetch some other information that relies upon the initial data fetched.
As you can see from the code, I am trying to do this using async / await, however whenever firing the two actions in my created() hook, the user's information isn't initialised, and therefore the second action fails.
My user store
async fetchCreds({ commit }) {
try {
firebase.auth().onAuthStateChanged(async function(user) {
const { uid } = user
const userDoc = await users.doc(uid).get()
return commit('SET_USER', userDoc.data())
})
} catch (error) {
console.log(error)
commit('SET_USER', {})
}
}
My club action which relies upon the above call
async fetchClubInformation({ commit, rootState }) {
try {
const clubIDForLoggedInUser = rootState.user.clubId
const clubDoc = await clubs.doc(clubIDForLoggedInUser).get()
return commit('SET_CLUB_INFO', clubDoc.data())
} catch (error) {
console.log(error)
}
}
}
The methods being called within my component's created() method.
created: async function() {
await this.fetchCreds();
await this.fetchClubInformation();
this.loading = false;
}
I have a feeling I'm fundamentally misunderstanding async / await, but I can't understand what in the code is incorrect - any help or advice would be greatly appreciated.
I'm not particularly familiar with Firebase but after a bit of digging through the source code I think I can shed a little light on your problems.
Firstly, consider the following example:
async function myFn (obj) {
obj.method(function () {
console.log('here 1')
})
console.log('here 2')
}
await myFn(x)
console.log('here 3')
Question: What order will you see the log messages?
Well here 2 will definitely come before here 3 but it's impossible to tell from the code above when here 1 will show up. It depends on what obj.method does with the function it's been passed. It might never call it at all. It might call it synchronously (e.g. Array's forEach method), in which case here 1 will appear before the other messages. If it's asynchronous (e.g. timers, server calls) then here 1 may not show up for some time, long after here 3.
The async modifier will implicitly return a Promise from the function if it doesn't return a Promise itself. The resolved value of that Promise will be the value returned from the function and the Promise will resolve at the point the function returns. For a function without a return at the end that's equivalent to it finishing with return undefined.
So, to stress the key point, the Promise returned by an async function will only wait until that function returns.
The method onAuthStateChanged calls its callback asynchronously, so the code in that callback won't run until after the surrounding function has completed. There's nothing to tell the implicitly returned Promise to wait for that callback to be invoked. The await inside the callback is irrelevant as that function hasn't even been called yet.
Firebase makes extensive use of Promises, so typically the solution would just be to return or await the relevant Promise:
// Note: This WON'T work, explanation follows
return firebase.auth().onAuthStateChanged(async function(user) {
// Note: This WON'T work, explanation follows
await firebase.auth().onAuthStateChanged(async function(user) {
This won't work here because onAuthStateChanged doesn't actually return a Promise, it returns an unsubscribe function.
You could, of course, create a new Promise yourself and 'fix' it that way. However, creating new Promises using new Promise is generally considered a code smell. Typically it's only necessary when wrapping code that doesn't support Promises properly. If we're working with a library that has proper Promise support (as we are here) then we shouldn't need to create any Promises.
So why doesn't onAuthStateChanged return a Promise?
Because it's a way of watching all sign-in/sign-out events. Every time the user signs in or signs out it'll call the callback. It isn't intended as a way to watch a particular sign-in. A Promise can only be resolved once, to a single value. So while a single sign-in event could be modelled with a Promise it's meaningless when watching all sign-in/sign-out events.
So fetchCreds is registering to be notified about all sign-in/sign-out events. It doesn't do anything with the returned unsubscribe function, so presumably it'll be listening to all such events until the page is reloaded. If you call fetchCreds multiple times it'll keep adding more and more listeners.
If you're waiting for a user to finish signing in then I suggest waiting for that directly instead. firebase.auth() has various methods starting with the prefix signIn, e.g. signInWithEmailAndPassword, and these do return a Promise that resolves when the user has finished signing in. The resolved value provides access to various information, including the user. I don't know which method you're using but the idea is much the same for all of them.
However, it might be that you're really just interested in grabbing the details of the current user. If that's all you want then you don't need to use onAuthStateChanged at all. You should just be able to grab a copy using the currentUser property. Something like this:
async fetchCreds({ commit }) {
try {
const { uid } = firebase.auth().currentUser
const userDoc = await users.doc(uid).get()
commit('SET_USER', userDoc.data())
} catch (error) {
console.log(error)
commit('SET_USER', {})
}
}
As I've already mentioned, this relies on the assumption that the user is already signed in. If that isn't a safe assumption then you might want to consider waiting until after sign in has completed before creating components that need user credentials.
Update:
Questions from the comments:
If the obj.method() call was asynchronous and we did await the callback function within it, would that ensure that the outer async function (myFn) never resolves before the inner one has finished?
I'm not entirely sure what you're asking here.
Just to be clear, I'm being very careful with my use of the words async and asynchronous. A function such as setTimeout would be considered asynchronous but it is not async.
async/await is just a lot of syntactic sugar around Promises. You don't really wait for a function, you wait for a Promise. When we talk about awaiting an async function we're really talking about waiting for the Promise it returns to resolve.
So when you say await the callback function it's not really clear what that means. Which Promise are you trying to await?
Putting the async modifier on a function doesn't make it magically wait for things. It will only wait when it encounters await. You can still have other asynchronous calls within an async function and, just like with a normal function, these calls will be performed after the function has returned. The only way to 'pause' is to await a Promise.
Putting an await inside another function, even a nested function, won't make any difference to whether the outer function waits unless the outer function is already waiting for the inner function. Behind the scenes this is all just Promises chaining then calls. Whenever you write await you're just adding another then call to a Promise. However, that won't have the desired effect unless that Promise is in the same chain as the Promise returned by the outer async function. It only needs one link to be missing for the chain to fail.
So modifying my earlier example:
async function myFn (obj) {
await obj.method(async function () {
await somePromise
// ...
})
// ...
}
await myFn(x)
Note that there are 3 functions here: myFn, method and the callback passed to method. The question is, will await myFn(x) wait for somePromise?
From the code above we can't actually tell. It would depend on what method does internally. For example, if method looked like this then it still wouldn't work:
function method (callback) {
setTimeout(callback, 1000)
}
Putting async on method won't help, that'll just make it return a Promise but the Promise still won't be waiting for the timer to fire.
Our Promise chain has a broken link. myFn and the callback are both creating their parts of the chain but unless method links those Promises together it won't work.
On the other hand, if method is written to return a suitable Promise that waits for the callback to complete then we will get our target behaviour:
function method (callback) {
return someServerCallThatReturnsAPromise().then(callback)
}
We could have used async/await here instead but there was no need as we can just return the Promise directly.
Also, if in the async myFn function you're not returning anything, does that mean it'll resolve immediately and as undefined?
The term immediately is not well-defined here.
If a function isn't returning anything at the end then it's equivalent to having return undefined at the end.
The Promise returned by an async function will resolve at the point the function returns.
The resolved value for the Promise will be the value returned.
So if you aren't returning anything it will resolve to undefined. Resolving won't happen until the end of the function is reached. If the function doesn't contain any await calls then this will happen 'immediately' in the same sense as a synchronous function returning 'immediately'.
However, await is just syntactic sugar around a then call, and then calls are always asynchronous. So while the Promise might resolve 'immediately' the await still has to wait. It's a very short wait, but it isn't synchronous and other code may get the opportunity to run in the meantime.
Consider the following:
const myFn = async function () {
console.log('here 3')
}
console.log('here 1')
Promise.resolve('hi').then(() => {
console.log('here 4')
})
console.log('here 2')
await myFn()
console.log('here 5')
The log messages will appear in the order they're numbered. So even though myFn resolves 'immediately' you'll still get here 4 jumping in between here 3 and here 5.
To make it short
fetchCreds({ commit }) {
return new Promise((resolve, reject) => {
try {
firebase.auth().onAuthStateChanged(async function(user) {
const { uid } = user
const userDoc = await users.doc(uid).get()
commit('SET_USER', userDoc.data())
resolve()
})
} catch (error) {
console.log(error)
commit('SET_USER', {})
resolve()
}}
}
async () => undefined // returns Promise<undefined> -> undefined resolves immediatly
asnyc () => func(cb) // returns Promise<any> resolves before callback got called
() => new Promise(resolve => func(() => resolve())) // resolves after callback got called
I have a function that resolves a Promise without passing any arguments:
const checkUser = (user)=> {
let promise = Parse.Promise();
if(user.success){
promise.resolve();
} else {
promise.reject("Error");
}
}
The problem is that in all tutorials that I read, they assign the returning value to a variable, like this:
let response = await checkUser(user);
In the above case, can I just await for the promise without assigning the result to a variable or this is not recommended? For example:
...
await checkUser(user);
...
Yes, you can totally do that. JavaScript will still wait for the promise to resolve.
Here is a modification of MDN's first example for await. It does not return a value but still waits for the Promise to resolve before running the code after await.
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(resolve, 2000);
});
}
(async function() {
console.log(1)
await resolveAfter2Seconds();
console.log(2);
})()
I think it is a bad practice to resolve a promise with nothing, as that's the appropriate channel for results. And if you don't want to get a result, why would you have called a function at all? This might apply to functional programming mostly, though.
Can I just await the promise without assigning the result to a variable or this is not recommended?
Yes, you can totally do that. If you don't need to do anything with the result - like when you know that it always is undefined - then you can just ignore it.
Using sinon and enzyme I wanna test the following component:
// Apple.js
class Apple extends Component {
componentDidMount = () => {
this.props.start();
Api.get()
.then(data => {
console.log(data); // THIS IS ALWAYS CALLED
this.props.end();
});
}
render () {
return (<div></div>);
}
}
If I just check endApy.called, it's always false. But wrapping it in a setTimeout will make it pass. Why console.log() is always called but not the props.end? Why setTimeout fixes it? Is there a better way of doing this?
// Apple.test.js
import sinon from 'sinon';
import { mount } from 'enzyme';
import Api from './Api';
import Apple from './Apple';
test('should call "end" if Api.get is successfull', t => {
t.plan(2);
sinon
.stub(Api, 'get')
.returns(Promise.resolve());
const startSpy = sinon.spy();
const endApy = sinon.spy();
mount(<Apple start={ startSpy } end={ endApy } />);
t.equal(startSpy.called, true); // ALWAYS PASSES
t.equal(endSpy.called, true); // ALWAYS FAILS
setTimeout(() => t.equal(endApy.called, true)); // ALWAYS PASSES
Api.get.restore();
});
Api.get is async function and it returns a promise, so to emulate async call in test you need to call resolves function not returns:
Causes the stub to return a Promise which resolves to the provided value. When constructing the Promise, sinon uses the Promise.resolve method. You are responsible for providing a polyfill in environments which do not provide Promise.
sinon
.stub(Api, 'get')
.resolves('ok');
your console.log(data) always happens because your Promise does resolve, it just does so after the test has finished, which is why the assertion fails.
By wrapping it in a setTimeout you create another event in the loop, which allows your Promise to resolve before the test finishes, meaning that your assertion will now pass.
This is a fairly common problem when unit testing asynchronous code. Often resolved in wrapping the assertions in setImmediate and calling done from the callback of setImmediate.
https://stackoverflow.com/a/43855794/6024903
I ran into a similar problem with a spy and using await Promise.all(spy.returnValues)worked fine for me. This resolves all the promises and afterwards I can check spy.called.