React-hooks-testing-library: Async hooks never resolve

Created on 31 Oct 2019  路  9Comments  路  Source: testing-library/react-hooks-testing-library

What is your question:

Hi, I'm trying to test an async hook, everything is working well (ie expect checks are validated) but the test never ends and the error
Error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
is triggered, making it impossible to test properly.

Here is the hooks I'm trying to test:

import { Dispatch, SetStateAction, useEffect, useState, useContext } from 'react';

import { SettingsContext } from 'src/utils/contexts';

import { get } from './requests';


function useFetch<T>(initialUrl: string): [FetchData<T>, Dispatch<SetStateAction<string>>] {
    const [url, setUrl] = useState(initialUrl);
    const { city } = useContext(SettingsContext);

    const [fetchState, setFetchState] = useState<FetchData<T>>({
        isReady: false,
        isLoading: false,
    });

    useEffect(() => {
        let didCancel = false;

        const fetchData = async () => {
            setFetchState({ isLoading: true, isReady: false });
            try {
                const formattedUrl = url.replace(':city', city.slug);
                const data: T = await get<T>(formattedUrl);
                if (!didCancel) {
                    setFetchState({ isLoading: false, isReady: true, data });
                }
            } catch (error) {
                if (!didCancel) {
                    setFetchState({ isLoading: false, isReady: false, error });
                }
            }
        };
        fetchData();

        return () => {
            didCancel = true;
        };
    }, [url]);

    return [fetchState, setUrl];
}

type FetchInit = {
    isReady: false,
    isLoading: false,
};

type FetchLoading = {
    isReady: false,
    isLoading: true,
};

type FetchSuccess<T> = {
    isReady: true,
    isLoading: false,
    data: T,
};

type FetchError = {
    isReady: false,
    isLoading: false,
    error: Error,
};

type FetchData<T> =
    | FetchInit
    | FetchLoading
    | FetchSuccess<T>
    | FetchError;

export default useFetch;

the test:

import { renderHook } from '@testing-library/react-hooks';

import { useFetch } from 'src/utils/requests';


jest.mock('src/utils/requests/requests', () => ({
    get: (endpoint: string) => (
        new Promise<FakeData>(resolve => resolve({ fake: endpoint }))
    ),
}));

describe('useFetch', () => {
    it('should fetch data', async () => {
        console.log('start')
        const { result, waitForNextUpdate } = renderHook(() => useFetch<FakeData>('url'));

        console.log('first check')
        expect(result.current[0].isLoading).toBeTruthy();
        expect(result.current[0].isReady).toBeFalsy();

        await waitForNextUpdate;

        console.log('second check')
        expect(result.current[0].isLoading).toBeFalsy();
        expect(result.current[0].isReady).toBeTruthy();
        if (result.current[0].isReady) {
            expect(result.current[0].data.fake).toBe('url');
        }

        console.log('should return')
    });
});

type FakeData = {
    fake: string
};
````

and the test log (with soooo many warnings):

console.log test/utils/requests/useFetch.spec.ts:14
start

console.log test/utils/requests/useFetch.spec.ts:17
first check

console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:102
Warning: An update to TestHook inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  /* fire events that update state */
});
/* assert on the output */

This ensures that you're testing the behavior the user would see in the browser. Learn more at https://fb.me/react-wrap-tests-with-act
    in TestHook
    in Suspense

console.log test/utils/requests/useFetch.spec.ts:23
second check

console.log test/utils/requests/useFetch.spec.ts:30
should return

console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:102
Warning: The callback passed to TestRenderer.act(...) function must not return anything.

It looks like you wrote TestRenderer.act(async () => ...) or returned a Promise from it's callback. Putting asynchronous logic inside TestRenderer.act(...) is not supported.

console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:102
Warning: Do not await the result of calling TestRenderer.act(...), it is not a Promise.

console.error node_modules/react-native/Libraries/YellowBox/YellowBox.js:130
It looks like you're using a version of react-test-renderer that supports the "act" function, but not an awaitable version of "act" which you will need. Please upgrade to at least [email protected] to remove this warning.

Error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
at mapper (/Users/pbo/Work/Staycation/foundation/mobile/node_modules/jest-jasmine2/build/queueRunner.js:25:45)
at promise.then (/Users/pbo/Work/Staycation/foundation/mobile/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
at process._tickCallback (internal/process/next_tick.js:68:7)
```

What did I do wrong?

The versions I'm using:

I tried installing the last version of [email protected] but it makes all my tests to fail :/

Thanks!

question

Most helpful comment

Ah, you also will need react-test-renderer v16.9.0 or higher for that to work. If you didn't also update react to the matching version then that is most likely why your tests failed.

All 9 comments

Try await waitForNextUpdate() instead and let me know if you still have issues.

Ah, you also will need react-test-renderer v16.9.0 or higher for that to work. If you didn't also update react to the matching version then that is most likely why your tests failed.

Hi,
Thanks for the quick reply.

I've already tested await waitForNextUpdate(), but when I did, it never passed this await 馃(ie console.log('second check') is never fired, and obviously timeout happens).

Same for [email protected], I tried installing it besides the expo's [email protected], but it's worse. No test work anymore, and for this one, I have a TypeError: Cannot read property 'current' of undefined error :/

Maybe I should wait for expo v36, which should have the last version of the test-renderer...

waitForNextUpdate() resolves when the test component rerenders, so to say it doesn't resolve implies it is either rendering synchronously before you start waiting or its not actually rendering again.

You could try using v2 of this library which did not rely on v16.9 of react/react-test-renderer, but just be aware that you will still see the warnings about not wrapping the updates in act (which is what v16.9 addressed)

I had the same issue and with version 2.0.3 seems to be working fine.

Hi, I downgraded to 2.0.3 and it's indeed working !
I'll be waiting for expo v36, should have the right version of the test renderer.

Thx!

I'm not familiar with expo or react-native in general. I think it might be useful to document this somehow in the readme and docs.

Yeah, that could be a good idea, I can do that.
Should I use say "when using expo, use v2.0" or do you have something else in mind ?

Yeah, I'm thinking like a warning that be is not compatible yet and to use v2 and ignore the act warning for async updates.

Perhaps mention the expected version of expo it will fixed in as well (not sure how guaranteed that is though)?

Was this page helpful?
0 / 5 - 0 ratings