Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 428
Berkeli Avatar asked Jun 09 '26 19:06

Berkeli


2 Answers

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

like image 150
Berkeli Avatar answered Jun 12 '26 12:06

Berkeli


Using act to call testing library methods is discouraged.

It's better to call the callback itself.

like image 26
Anyul Rivas Avatar answered Jun 12 '26 11:06

Anyul Rivas



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!