Skip to content
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

Unable to mock setTimeout after upgrading to v8.0.0 #992

Closed
evoyy opened this issue Jul 8, 2021 · 3 comments
Closed

Unable to mock setTimeout after upgrading to v8.0.0 #992

evoyy opened this issue Jul 8, 2021 · 3 comments

Comments

@evoyy
Copy link

evoyy commented Jul 8, 2021

I use the following technique to temporarily override the global setTimeout to always use a delay of zero. This makes all my transitions instant and my test suite runs nice and fast.

I have the following helper function:

function useZeroTimers(cb) {
    return new Promise((resolve, reject) => {
        let setTimeout = globalThis.setTimeout;
        globalThis.setTimeout = cb => setTimeout(cb, 0);

        Promise.resolve(cb())
            .then(() => {
                globalThis.setTimeout = setTimeout;
                resolve();
            })
            .catch(error => {
                globalThis.setTimeout = setTimeout;
                reject(error);
            });
    });
}

And I use it in my tests like this:

it('should do something', async function() {
    await useZeroTimers(async () => {

        // Any calls to setTimeout within this scope will use the
        // temporarily-overridden setTimeout with a delay of zero.

        doSomething();

        await waitFor(() => {
            expect(something).to.have.been.called;
        });
    });

    // outside the scope, setTimeout is magically restored
});

It's a nice technique that I think is simple and elegant and has worked well for me for a long time.

After upgrading react-testing-library to v12.0.0, this technique no longer works. All my tests that use my useZeroTimers helper are broken.

I noticed the following in the release notes for v8.0.0:

The timeout in waitFor(callback, { interval, timeout } ) now uses the same clock as interval. Previously timeout was always using the real clock while interval was using the global clock which could've been mocked out. For the old behavior I'd recommend waitFor(callback, { interval, timeout: Number.POSITIVE_INFINITY }) and rely on your test runner to timeout considering real timers.

I'm not entirely sure what's going on, but I think that as my useZeroTimers technique modifies the global setTimeout, it is not compatible with v8.0.0. Is there any workaround that can be suggested for this? If not, could I suggest an option to tell waitFor to behave as it did before v8.0.0?

@evoyy
Copy link
Author

evoyy commented Jul 8, 2021

I think I have resolved this. The simple solution was to reduce the scope of my useZeroTimers helper to prevent waitFor receiving a mocked timer. I also needed to re-architect my strategy for some tests that involve polling.

@evoyy evoyy closed this as completed Jul 8, 2021
@evoyy
Copy link
Author

evoyy commented Jul 8, 2021

After spending some time on this I have decided to re-open the issue, as the change introduced in v8.0.0 has limited my ability to test sequences of events in my app that are scheduled using setTimeout.

For instance, I have a prompt component to confirm the deletion of an item: Clicking on "delete" opens the prompt, which is faded into view using setTimeout with a delay of 200ms. Then clicking on "Yes" triggers the deletion and the item is faded out, using a setTimeout, with another delay of 200ms. If the deletion fails, an error message is displayed for 10 seconds and then finally the prompt is reverted back to its initial state after a 10 second timeout.

The only way I can test this functionality is by mocking setTimeout to always use a zero delay. This is no longer possible in v8.0.0.

Can anyone offer any advice here?

@evoyy evoyy reopened this Jul 8, 2021
@eps1lon
Copy link
Member

eps1lon commented Jul 8, 2021

Thanks for the feedback. Closing as a duplicate of #987 since you're facing the same problem: How to mock timers outside of jest?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants