-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide an API to flush the Promise resolution queue #2157
Comments
While async testing promises it's good to remember that you can return a test function as a Promise, so something like this will work: test('my promise test', () => { //a test function returning a Promise
return Promise.resolve(load.succeed(result))
.then(() => {})
.then(() => expect(result).toHaveBeenCalledWith(result));
}) Returning a Promise from test function makes Jest aware that this is a async test and to wait until it's resolved or times out. |
@thymikee Of course I'm returning the value to make Jest wait - that's completely off the point. Note how you even left the line I've added the |
Ran into a similar problem, and described it here: https://github.com/pekala/test-problem-example In short: I'm trying to assert on sequence of actions dispatched to the Redux store as a result of user interaction (simulated using enzyme). The actions as dispatched sync and async using Promises (mocked to resolve immidiately). There seems to be no way to assert after the Promise chain is exhaused, if you don't have direct access to the promise chain. The idea of |
As a follow-up: I tried replacing test('changing the reddit downloads posts', done => {
setImmediate(() => {
// assertions...
done()
})
}) |
A helper function can turn that into a promise itself so you don't need to deal with the done callback. It's small enough it's pretty harmless to keep in userland, but I wouldn't complain if it was put on the jest object. Something like this gets used a lot in my projects. function flushPromises() {
return new Promise(resolve => setImmediate(resolve));
}
test('', () => {
somethingThatKicksOffPromiseChain();
return flushPromises().then(() => {
expect(...
});
}) With async await it's almost pretty: test('', async () => {
somethingThatKicksOffPromiseChain();
await flushPromises();
expect(...
}) |
@jwbay that's some nice sugar right there 🍠! It's true that this |
@pekala the one liner IMO doesn't provide the required behavior because it will not wait until the following pending promise is resolved: function foo() {
return new Promise((res) => {
setTimeout(() => {
res()
}, 2000);
});
} What about swizzling Promise and when a new Promise is created add it to some array then flush all promises will await on Promise.all over this array? |
@talkol I think it will, as long as you us the fake timers as well. I haven't tested that though. |
@pekala no need to fake timers with this example since the promise will resolve only after the time is reached |
If you don't fake timers, your tests will take real 2s+ to complete. I think the best practice would be to remove these types of delays, in which case the |
All depends on what you're trying to test :) All I'm saying is the timers are an unrelated concern to waiting for the promises |
We're hitting issues related to Promises not resolving, which are intermixed with setTimeout calls. In jest v19.0.2 we have no problems, but in jest v20.0.0 Promises never enter the resolve/reject functions and so tests fail. Our issue seems to be related this issue of not having an API to flush the Promise resolution queue, but this issue seems to pre-date jest v20.0.0 where we started to see the issue, so I'm not completely sure. |
This is only solution we've been able to come-up with for some of our tests, since we have a series of alternating
Not so pretty, so any advice here greatly appreciated. |
Another example where you cannot return promise from test:
This test fails with jest 20.0.4. |
@philwhln 's solution also can be written with async/await
|
I would love a utility function that flushed the promise queue |
I would love a function that flushes the promise queues between tests also. I'm testing code that uses Promise.all to wrap multiple promises. When one of those wrapped promises fails (because that is what I want to test) the promise immediately returns meaning the other promises sometimes (race condition, non deterministic) return while the next test is running. This causes all sorts of havoc with my tests having non predictable/repeatable outcomes. |
To properly implement this, we'd need to mock There's already an API to flush the micro tasks enqueued with |
I had a solution with jasmine that hooked into the nextTick of Yaku, a promise library and caught nextTick calls and allowed playing them early. |
I am using enzyme to mount React components. I, too, have functions that expect Promises to execute, but none of the aforementioned fixes worked. I would be able to handle them synchronously in my test - if - the functions returned the Promise objects, using This is the workaround that I ended up doing using a spy on the global Promise function.
|
my usecase: public static resolvingPromise<T>(result: T, delay: number = 5): Promise<T> {
return new Promise((resolve) => {
setTimeout(
() => {
resolve(result);
},
delay
);
});
} test file:it("accepts delay as second parameter", async () => {
const spy = jest.fn();
MockMiddleware.resolvingPromise({ mock: true }, 50).then(spy);
jest.advanceTimersByTime(49);
expect(spy).not.toHaveBeenCalled();
jest.advanceTimersByTime(1);
await Promise.resolve(); // without this line, this test won't pass
expect(spy).toHaveBeenCalled();
}); |
anybody come up with something with async_hooks ? |
Is there any workaround that works with the modern fake timers, promises ( I think many of us has a lot of code where promises should resolve after advancing timers (using |
IMO, a clean solution to this issue is making timer mocks async and advising users to include lint rules about unhandled async. So for example:
changes to: it("accepts delay as second parameter", async () => {
const spy = jest.fn();
MockMiddleware.resolvingPromise({ mock: true }, 50).then(spy);
await jest.advanceTimersByTime(49);
expect(spy).not.toHaveBeenCalled();
await jest.advanceTimersByTime(1);
expect(spy).toHaveBeenCalled();
}); In the meantime, perhaps simply documenting |
@thesmart and that solution ( |
Not sure, honestly. Probably would need to create some test cases for either |
Maybe this is off topic but I really need some help with mocking async / await functions. I want to test this async / await function using jest/enzyme
which occurs on
What I tried
This is what I got - ```
|
@dobradovic It is off-topic indeed. I'd recommend StackOverflow, unless you'd like to report a bug, in which case a new, separate issue would do the trick. |
The tests mock JS setTimeout API. However promise.resolve() is not working without flushing the promise queue (which could be done just by awaiting Promise.resolve()), similar issue has been discussed in jestjs/jest#2157.
This solution was broken for the JSDOM environment in Jest 27, via the removal of Removal: #11222 An equal implementation of One solution is switching to setTimeout, and temporarily forcing real timers. For illustration: return new Promise(resolve => {
jest.useRealTimers();
setTimeout(() => resolve(), 0);
jest.useFakeTimers();
}); Now we obviously don't want to force fake timers if they weren't fake already. I seem to be able to tell whether timers are faked via @SimenB short of a fully supported jest.withRealTimers(() => {
// code here is always run with real timers
});
// original timer state restored -- whether real, legacy, or modern This could be nice even once legacy is removed, since the test code doesn't need to try and detect the current timer state. Alternately, has anyone else worked around this differently? I tried process.nextTick and that works some of the time for legacy, but modern timers mock nextTick. |
@jwbay we've been using
|
The following implementation was working just fine (and still does when used with legacy timers): global.flushPromises = () => new Promise(process.nextTick) However, it is not working with modern timers because |
With idea from @acontreras89 , I don't need to |
I've hit a similar problem where I'm trying to test code which automatically retries a series of asynchronous operations. It uses a combination of promises and timers internally, and can't always expose a promise or callback to signal completion. Based on the suggestion by @stephenh, here's a utility function I wrote to help my tests wait for all pending timers and promises:
The loop is there to catch timers which were scheduled following resolution of other promises. It definitely isn't suitable for all situations (e.g. recursive timers), but it's useful in some cases. |
@peter-bloomfield Thanks a lot! Your snippet worked like a charm. I have the exact same situation, I have a fetch implementation that retries the requests using
|
The tests mock JS setTimeout API. However promise.resolve() is not working without flushing the promise queue (which could be done just by awaiting Promise.resolve()), similar issue has been discussed in jestjs/jest#2157.
This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 30 days. |
Still think this makes sense as a util helper. I've used |
Do you want to request a feature or report a bug?
Feature, I guess, but a pretty important one when testing code that uses
Promise
s.What is the current behavior?
I have a component that uses
Promise
wrapping and chaining internally in the follow-up to an external asynchronous action. I'm providing the mock of the async action and resolving the promise it returns in my test.The component something like this:
And the test code looks something like this:
The test fails because the
expect()
is evaluated before the chained promise handlers. I have to replicate the length of the inner promise chain in the test to get what I need, like this:What is the expected behavior?
I'd expect Jest to provide some sort of an API to flush all pending promise handlers, e.g.:
I've tried
runAllTicks
andrunAllTimers
to no effect.Alternatively, if I'm just missing some already existing feature or pattern, I'm hoping for someone here to point me in the right direction :)
The text was updated successfully, but these errors were encountered: