Fake timers behave unexpectedly with `Promise.race()`.
See original GitHub issueš Bug Report
With jest.useFakeTimers
(either āmodernā or ālegacyā), a Promise.race
between a short sleep Promise and a long sleep Promiseā¦resolves to the long oneās resolution, if the short one uses .then
.
An identical Promise.race
using real timers behaves as expected.
To Reproduce
For example, with modern fake timers (but also observed with the legacy fake timers):
describe('modern fake timers', () => {
jest.useFakeTimers('modern');
test('Promise.race without "then"', async () => {
const shortPromise = new Promise(resolve => setTimeout(() => resolve('short'), 1000));
const longPromise = new Promise(resolve => setTimeout(() => resolve('long'), 2000));
const racedPromise = Promise.race([shortPromise, longPromise]);
jest.runAllTimers();
const result = await racedPromise;
expect(result).toBe('short');
});
test('Promise.race with "then"', async () => {
const shortPromise = new Promise(resolve => setTimeout(resolve, 1000)).then(() => 'short');
const longPromise = new Promise(resolve => setTimeout(() => resolve('long'), 2000));
const racedPromise = Promise.race([shortPromise, longPromise]);
jest.runAllTimers();
const result = await racedPromise;
// Surprise! It's 'long'.
expect(result).toBe('short');
});
});
The second test, āPromise.race with āthenāā, fails.
Expected behavior
I would expect the second test, āPromise.race with āthenāā, to succeed; the shorter sleep should āwinā the race, same as the version without āthenā. An identical test succeeds if you do jest.useRealTimers()
.
Link to repl or repo (highly encouraged)
Here, we have with-āthenā and without-āthenā tests for each of the following:
- Modern fake timers (surprising behavior observed)
- Legacy fake timers (surprising behavior observed)
- Real timers (behaves as expected)
https://github.com/chrisbobbe/jest-modern-timers-race-then
envinfo
System:
OS: macOS 10.15.5
CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Binaries:
Node: 10.20.1 - ~/.nvm/versions/node/v10.20.1/bin/node
Yarn: 1.22.4 - /usr/local/bin/yarn
npm: 6.14.4 - ~/.nvm/versions/node/v10.20.1/bin/npm
npmPackages:
jest: ^26.1.0 => 26.1.0
Issue Analytics
- State:
- Created 3 years ago
- Reactions:3
- Comments:5
Top GitHub Comments
Running into similar issues with multiple timers and promises. Wouldnāt it help to have an asynchronous version of
jest.runAllTimers()
ā i.e. one that wouldsetImmediate()
between timer firings? This assumes promise implementation uses an equivalent ofnextTick()
to schedule the callback execution. Basically we need to be sure that all promise chains resolvable from a given timer would fully resolve themselves before the logically next (i.e. set to a later time) timer would fire.By the looks of it, got to be a one-liner ā @sinonjs/faketimers runAllAsync() is already available but not used by Jest.
https://github.com/facebook/jest/blob/412954fd260f17976a450bd3b252a5d7e2488a64/packages/jest-fake-timers/src/modernFakeTimers.ts#L50
Can either add
runAllTimersAsync()
or a booleanasync
parameter to the existing callā¦ For the time being, perhapscould be used as a kludge.
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.