上下文:我正在尝试为自定义 React hook 编写测试用例,并在内部使用与时间相关的 api(如 setInterval)。我使用
vi.useFakeTimers
来包装与时间相关的 api,并使用 renderHook
中的 @testing/library/react
来渲染挂钩。
问题:我正在尝试测试模拟回调函数是否在给定时间跨度内被调用
n
次,但它甚至没有被调用一次。
这是自定义的反应钩子。
// useInterval.ts
import { useCallback, useEffect, useRef } from "react";
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef<typeof callback>();
useCallback(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current?.();
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
export default useInterval;
这是测试文件。
// useInterval.test.ts
import { vi, expect, beforeEach, it, afterEach } from "vitest";
import { renderHook } from "@testing-library/react";
import useInterval from "./useInterval";
describe("useInterval", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
test("should call the callback after the specified delay", () => {
const mockCallback = vi.fn();
const delay = 100;
renderHook(() => useInterval(mockCallback, delay));
vi.advanceTimersByTime(200);
expect(mockCallback).toBeCalled(); // -> AssertionError: expected "spy" to be called at least once
});
});
这是我的依赖版本 -
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.0.0",
"jsdom": "^24.0.0",
"vitest": "1.3.1"
我认为您错误地使用了
useCallback
钩子而不是 useEffect
钩子来缓存回调函数。 useCallback
钩子会记住 并返回 一个函数,然后需要调用该函数以便主体执行并实际设置 savedCallback
引用值。当依赖项发生变化时,会调用 useEffect
钩子回调。
将
useCallback
替换为 useEffect
挂钩,以便传递的 callback
引用正确缓存在 savedCallback
引用中。
import { useEffect, useRef } from "react";
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef<typeof callback>();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current?.();
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
export default useInterval;