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.
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
Using act to call testing library methods is discouraged.
It's better to call the callback itself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With