无法在 vitest 中测试与时间相关的钩子

问题描述 投票:0回答:1

上下文:我正在尝试为自定义 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"
react-hooks react-testing-library vitest
1个回答
0
投票

我认为您错误地使用了

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;
© www.soinside.com 2019 - 2024. All rights reserved.