Reputation: 1572
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.
Upvotes: 8
Views: 8808
Reputation: 45780
Shouldn'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!
})
Upvotes: 8
Reputation: 23705
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
Upvotes: 1
Reputation: 35493
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);
});
Upvotes: 4