React jest钩子测试:waitFor内部异步代码



我有以下钩子:

import { useEffect, useRef, useState } from "react";
function useAsyncExample() {
const isMountedRef = useRef(false);
const [hasFetchedGoogle, setHasFetchedGoogle] = useState(false);
useEffect(() => {
if (!isMountedRef.current) {
isMountedRef.current = true;
const asyncWrapper = async () => {
await fetch("https://google.com");
setHasFetchedGoogle(true);
};
asyncWrapper();
}
}, []);
return hasFetchedGoogle;
}

使用以下jest测试(使用msw和react钩子测试库(:

import { act, renderHook } from "@testing-library/react-hooks";
import { rest } from "msw";
import mswServer from "mswServer";
import useAsyncExample from "./useAsyncExample";
jest.useFakeTimers();
describe("using async hook", () => {
beforeEach(() =>
mswServer.use(
rest.get("https://google.com/", (req, res, ctx) => {
return res(ctx.json({ success: ":)" }));
})
)
);
test("should should return true", async () => {
const { result, waitFor, waitForNextUpdate, waitForValueToChange } = renderHook(() => useAsyncExample());
// ... things I tried
});
});

我只是想等待setHasFetchedGoogle呼叫。

我尝试了多种方法:

await waitForNextUpdate(); // failed: exceeded timeout of max 5000 ms
await waitForValueToChange(() => result.current[1]); // failed: exceeded timeout of max 5000 ms
await waitFor(() => result.current[1])  // failed: exceeded timeout of max 5000 ms

到目前为止,我最接近的是以下内容:

const spy = jest.spyOn(global, "fetch");
// ...
await waitFor(() => expect(spy).toHaveBeenCalledTimes(1));
expect(spy).toHaveBeenLastCalledWith("https://google.com");

但即使这样也会在setHasFetchedGoogle调用发生之前结束,因为它只等待获取。

在网上,我发现了很多组件的例子,在这些例子中,您可以waitFor一个元素或文本出现。但是这在钩子中是不可能的,因为我没有呈现任何DOM元素。

我如何才能监听钩子的内部异步逻辑?我认为waitForNextUpdate正是出于这个目的,但它对我不起作用

感谢您的帮助!

事实证明,我的示例可以按照我的意愿运行

问题是,在我遇到的实际情况中,自定义钩子更复杂,并且内部有其他使用setTimeouts的逻辑。因此,我启用了jest.useFakeTimers

显然jest.useFakeTimerswaitForNextUpdate不能协同工作。

查看更多信息

我现在将尝试通过在需要时启用/禁用fakeTimers来让我的测试正常工作

正如您在回答中所说,您正在使用jest.useFakeTimers,但您说它不适用于waitForNextUpdate是不正确的,因为它确实适用。

下面是一个例子。我已经通过等待两秒钟将您对谷歌的请求修改为简单的异步事件。不过,对于一个实际的模拟请求,一切都应该是一样的。

const wait = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay))
function useAsyncExample() {
const isMountedRef = useRef(false);
const [hasFetchedGoogle, setHasFetchedGoogle] = useState(false);
useEffect(() => {
if (!isMountedRef.current) {
isMountedRef.current = true;
const asyncWrapper = async () => {
await wait(200);
setHasFetchedGoogle(true);
};
asyncWrapper();
}
}, []);
return hasFetchedGoogle;
}
// The test, which assumes a call to jest.useFakeTimers occurred in some beforeEach.
it('should should return true', async () => {
const { result, waitForNextUpdate } = renderHook(() => useAsyncExample())
expect(result.current).toBe(false)
act(() => {
jest.advanceTimersByTime(200)
})
await waitForNextUpdate()
expect(result.current).toBe(true)
})

最新更新