I have a function that awaits multiple promises
const function = async () => {
await function1()
await function2()
await function3()
}
I want to test that function3 is called:
it(('calls function3', async () => {
jest.spyOn(api, 'function1').mockResolvedValue({})
jest.spyOn(api, 'function2').mockResolvedValue({})
spy = jest.spyOn(api, 'function3')
await function()
expect(spy).toBeCalledTimes(1)
})
and this test fails, but when I call await a lot of times:
it(('calls function3', async () => {
jest.spyOn(api, 'function1').mockResolvedValue({})
jest.spyOn(api, 'function2').mockResolvedValue({})
spy = jest.spyOn(api, 'function3')
await await await await await function()
expect(spy).toBeCalledTimes(1)
})
the test will pass. Why is this? Shouldn't await function()
resolve all of the promises before moving onto the next expect line?
edit: The deeper the awaited function is i.e. a function4, the more await statements I need, but its not 1 to 1.
I have a function that awaits multiple promises
const function = async () => {
await function1()
await function2()
await function3()
}
I want to test that function3 is called:
it(('calls function3', async () => {
jest.spyOn(api, 'function1').mockResolvedValue({})
jest.spyOn(api, 'function2').mockResolvedValue({})
spy = jest.spyOn(api, 'function3')
await function()
expect(spy).toBeCalledTimes(1)
})
and this test fails, but when I call await a lot of times:
it(('calls function3', async () => {
jest.spyOn(api, 'function1').mockResolvedValue({})
jest.spyOn(api, 'function2').mockResolvedValue({})
spy = jest.spyOn(api, 'function3')
await await await await await function()
expect(spy).toBeCalledTimes(1)
})
the test will pass. Why is this? Shouldn't await function()
resolve all of the promises before moving onto the next expect line?
edit: The deeper the awaited function is i.e. a function4, the more await statements I need, but its not 1 to 1.
Share Improve this question edited Jun 13, 2019 at 17:07 skyboyer 23.7k7 gold badges62 silver badges71 bronze badges asked Jun 12, 2019 at 13:57 peterpeter 1,5825 gold badges18 silver badges41 bronze badges 2-
1
function
is a javascript reserved word, are you sure your function declaration does not throw any error? – Dario Commented Jun 12, 2019 at 15:02 - @Dario sorry this is an example and not actual code – peter Commented Jun 12, 2019 at 16:58
3 Answers
Reset to default 8Shouldn't
await function()
resolve all of the promises before moving onto the next expect line?
Yes, await
will wait for the returned Promise
before continuing.
Here is a simple working example:
const function1 = jest.fn().mockResolvedValue();
const function2 = jest.fn().mockResolvedValue();
const function3 = jest.fn().mockResolvedValue();
const func = async () => {
await function1();
await function2();
await function3();
}
it('calls function3', async () => {
await func();
expect(function3).toHaveBeenCalled(); // Success!
})
If await
is not waiting as long as expected, then the Promise
chain is likely broken at some point.
Here is an example of a broken Promise
chain:
const function1 = jest.fn().mockResolvedValue();
const function2 = jest.fn().mockResolvedValue();
const function3 = jest.fn().mockResolvedValue();
const func = async () => {
await function1();
await function2();
await function3();
}
const func2 = async () => {
func(); // <= breaks the Promise chain
}
it('calls function3', async () => {
await func2();
expect(function3).toHaveBeenCalled(); // <= FAILS
})
Calling await
multiple times will queue the rest of the test function at the back of the PromiseJobs
queue multiple times which can give pending Promise
callbacks a chance to run...
...so the broken test above will pass if it is changed to this:
it('calls function3', async () => {
await await await await func2(); // <= multiple await calls
expect(function3).toHaveBeenCalled(); // Success...only because of multiple await calls
})
...but the real solution is to find and fix where the Promise
chain is broken:
const func2 = async () => {
await func(); // <= calling await on func fixes the Promise chain
}
it('calls function3', async () => {
await func2();
expect(function3).toHaveBeenCalled(); // Success!
})
It is a matter of the order that the promises are enqueued in the micro-task queue, I'm using flush-promises
to resolve the same issue.
It uses nodes setImmediate
that pushes to the queue a callback that will be called when the micro-task queue is empty.
const flushPromises = require('flush-promises');
test('flushPromises', async () => {
let a;
let b;
Promise.resolve().then(() => {
a = 1;
}).then(() => {
b = 2;
})
await flushPromises();
expect(a).toBe(1);
expect(b).toBe(2);
});
By now there is a proposal in Jest having something like runAllTimers
but for promises.
So if you'd like to avoid integrating flush-promises
you may just use setTimeout(() => {...rest code...}, 0)
. Since timeout
is macrotask it's guaranteed all pending microtasks(like promises) are resolved before running that.
More on microtasks and macrotasks: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f