Search code examples
reactjsreact-hooksreact-testing-library

Cannot test custom hooks with React 18 and renderHook from testing-library/react


I have a cutom hook that makes an API call on mount and handles state (isLoading, isError, data, refetch);

The hook is quite simple:

    const useFetch = (endpoint, options) => {
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);
    const [isLoading, setIsLoading] = useState(true);
    const [trigger, setTrigger] = useState(true);

    const triggerSearch = () => {
        setTrigger(!trigger);
    };

    useEffect(() => {
        const fetchData = async () => {
            try {
                const response = await fetch(
                    `${process.env.API_URL}${endpoint}`
                );
                const json = await response.json();
                setData(json);
                setIsLoading(false);
            } catch (error) {
                setError(error);
                setIsLoading(false);
            }
        };
        fetchData();
    }, [endpoint, trigger]);
    return {
        data,
        isLoading,
        error,
        triggerSearch,
    };
};

When trying to test the hook, I'm using jest and testing-library/react.

With react 18, the react-hooks from testing-library is no longer supported so I cannot use awaitForNextUpdate from renderHook as it doesn't return it.

Instead, we should use act and waitFor - which I have done and tests pass.

The problem is that I get the following error

Warning: An update to TestComponent inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):
test("should make an API call on mount", async () => {
    const hook = renderHook(() => useFetch("/api"));

    await act(async () => {
        await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1));
    });

    expect(fetch).toHaveBeenCalledTimes(1);

    expect(getAccessTokenSilently).toHaveBeenCalledTimes(1);

    expect(hook.result.current.data).toEqual({ message: "Test" });
    expect(hook.result.current.isLoading).toEqual(false);
    expect(hook.result.current.error).toEqual(null);
});

Could someone please point me in the right direction? I have tried removing all of the assertions and just calling renderHook, which also results in the same error.


Solution

  • So the way I resolved the issue was to include everything that calls setState function inside async act (including the initialization of renderhook, because it calls the api via useEffect):

      describe("useFetch", () => {
        test("should make a call to the API and return the message", async () => {
            let hook;
            await act(async () => {
                hook = renderHook(() => useFetch("/api"));
            });
            const { result } = hook;
            expect(fetch).toHaveBeenCalledTimes(1);
            expect(getAccessTokenSilently).toHaveBeenCalledTimes(1);
            expect(result.current.data).toEqual({ message: "Test" });
            expect(result.current.isLoading).toEqual(false);
            expect(result.current.error).toEqual(null);
        });
    });
    

    Oh and make sure you are importing act from @testing-library/react