Open hon2a opened 7 years ago
@thesmart and that solution (
await Promise.resolve();
) works when usingmodern
, no matter of the amount of promises that need to resolve in a chain?
Not sure, honestly. Probably would need to create some test cases for either modern
or legacy
as part of a patch. The default is now modern
(see: sinonjs) but I'm not sure what version of Jest aligned w/ the test case above. Whatever the documented workaround is, it should note what to do for modern
and/or legacy
.
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
deleteAndReset = async () => {
await this.submitDeleteMeeting();
this.props.resetMeetingState();
}
which occurs on
<BackButton
data-test="backButton"
onClick: this.deleteAndReset
/>
What I tried
describe('Delete and reset on click', () => {
const props = {
intl,
deleteAndReset: jest.fn(),
submitDeleteMeeting: jest.fn()
};
it('on click call deleteAndReset', async () => {
component = shallow(<StartMeeting {...props} />);
const backButton = findByDataTestAttr(component, 'backButton');
expect(backButton.exists()).toEqual(true);
const instance = component.instance();
backButton.props().onClick();
await Promise.resolve();
expect(instance.props.submitDeleteMeeting).toHaveBeenCalled();
});
});
This is what I got - ``` "expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0"
What I am doing wrong with async / await testing?
Sorry for a little bit longer post but this "simple" thing drives me crazy for long time.
Any advice and suggest would be helpful, thanks for your time! :)
@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.
This solution was broken for the JSDOM environment in Jest 27, via the removal of setImmediate
: https://github.com/facebook/jest/issues/2157#issuecomment-279171856
Removal: https://github.com/facebook/jest/pull/11222
An equal implementation of waitForPromises
now gets a lot trickier, complicated further by the fact that setImmediate in JSDOM apparently bypassed fake timers. So moving to another timer means we need to handle faking.
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 jest.isMockFunction(setTimeout)
✔️. However, I can't tell which timer style is currently being used; legacy vs. modern ❌. For a widely used function to suddenly need this context to work is quite painful.
@SimenB short of a fully supported jest.waitForPromises
, would you instead consider a new Jest API like:
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 requireActual
:
export function flushPromises(): Promise<void> {
return new Promise(jest.requireActual("timers").setImmediate);
}
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 process.nextTick
is also mocked. I am curious to know why, since @sinonjs/fake-timers
does not do this by default (see the default value of config.toFake
)
With idea from @acontreras89 , I don't need to flushPromises
, just let Jest doNotFake
nextTick
to make Promise works.
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:
const waitForPromisesAndFakeTimers = async () => {
do {
jest.runAllTimers();
await new Promise(jest.requireActual('timers').setImmediate);
} while (jest.getTimerCount() > 0);
};
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 setTimeout
and wrapping them in a promise each time. I wanted to extract the value out of the promise so I had to await it again:
const requestPromise = request(requestUrl)
await waitForPromisesAndFakeTimers();
const { status } = await requestPromise
expect(status).toEqual('success');
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 flushPromises()
in all projects since I started following this.
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 :)