Skip to content

renderHook fails for suspense-based hooks using jest.useFakeTimers #589

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

Closed
marcospassos opened this issue Apr 6, 2021 · 1 comment
Closed
Labels
bug Something isn't working

Comments

@marcospassos
Copy link

marcospassos commented Apr 6, 2021

Problem description:

We have a cache-related test that requires mocking the setTimeout to assert the cache has been cleaned after a given time interval. However, the renderHook seems to do not work when using fake times.

  • react-hooks-testing-library version: 5.1.1
  • react version: 16.0.0
  • react-dom version (if applicable): 16.0.0

What happened:

Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.

Reproduction:

 jest.useFakeTimers();

const cache: {promise?: Promise<any>, value?: any} = {};

const useExample = () => {
    if (cache.value !== undefined) {
        return cache.value;
    }

    if (!cache.promise) {
        cache.promise = new Promise(resolve => setTimeout(() => resolve('foo'), 10))
            .then(result => {
                cache.value = result;
            });
    }

    throw cache.promise;
};

it('should not fail', async () => {
    const {result, waitForNextUpdate} = renderHook(() => useExample());

    jest.runAllTimers();

    await waitForNextUpdate();

    expect(result.current).toBe('foo');
});
@marcospassos marcospassos added the bug Something isn't working label Apr 6, 2021
@marcospassos marcospassos changed the title renderHook fails for suspense-based hooks using useFakeTimers renderHook fails for suspense-based hooks using jest.useFakeTimers Apr 6, 2021
@mpeyper
Copy link
Member

mpeyper commented Apr 7, 2021

Hi @marcospassos,

Disclaimer: I am not an expert in any of this so this is my understanding of what is going on but there may be some inaccuracies of mistakes in my mental model of it all.

For starters, this isn't an issue with @testing-library/react-hooks but rather trying to mix promises and fake timers with jest. Running jest.runAllTimers() does trigger the timeout to fire, however, the then (and other microtasks) of the promise does not run until the test awaits as it is always asynchronous.

You can work around this by:

  1. awaiting something else that will resolve to give your promise a chance to flush its microtasks
  2. using our rerender functionality to force the new value to be returned
    • I'm not actually sure why this is required as the promise resolving should be enough to trigger the render, but I suspect there is something deep in the guts of the react renderer (react-dom based on the provided info) that needs the promise to be flushed.

The simplest way I could find to do this was:

it("should not fail", async () => {
  const { result, rerender } = renderHook(() => useExample());

  jest.runAllTimers();

  await Promise.resolve();

  rerender()

  expect(result.current).toBe("foo");
});

Secondly, the async utils are used when the test needs to wait for async behaviour in a hook. By faking the timers, the updates are no longer asynchronous (excluding the above described weirdness of mixing timeouts and promises). An alternative approach would be to not use fake timers and allow an async util to actually wait for it to happen:

// do not call jest.useFakeTimers();

it("should not fail", async () => {
  const { result, waitForNextUpdate } = renderHook(() => useExample());

  await waitForNextUpdate()
  
  expect(result.current).toBe("foo");
});

This results in a slightly slower test, however, I feel that not faking timers allows the test to be more robust to changes in the implementation on the test.

I hope this helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants