1

这是我的自定义 React-hook。

import { useEffect, useRef } from 'react'

function useInterval({ callback, interval, delay }) {
  const savedTimerId = useRef<NodeJS.Timeout>()

  useEffect(() => {
    const loop = () => {
      const res = callback()
      const nextIteration = () => {
        savedTimerId.current = setTimeout(loop, interval)
      }
      if (res instanceof Promise) {
        res.then(nextIteration)
      } else {
        nextIteration()
      }
    }
    let delayedTimerId: NodeJS.Timeout
    if (!delay) {
      loop()
    } else {
      delayedTimerId = setTimeout(loop, delay)
    }
    return () => {
      // @ts-ignore
      clearTimeout(savedTimerId.current)
      if (delayedTimerId) {
        clearTimeout(delayedTimerId)
      }
    }
  }, [callback, interval, delay])
}

export { useInterval }

这是单元测试

import { renderHook } from '@testing-library/react-hooks'
import { useInterval } from '../useInterval'

describe("Test scenarios for 'useInterval' hook", () => {
  jest.useFakeTimers()

  it("Should call 'callback' once", () => {
    const callback = jest.fn()
    const interval = 10000
    const params = { callback, interval }
    renderHook(() => useInterval(params))
    expect(setTimeout).toHaveBeenCalledTimes(1)
    expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), interval)
  })
})

但这就是输出

Error: expect(jest.fn()).toHaveBeenCalledTimes(expected)

Expected number of calls: 1
Received number of calls: 2

我调试了这个片段。我发现在useInterval调用之前已经触发了一些东西setTimeout调试结果

似乎setTimeout已在内部调用。做错了什么?有任何想法吗?

4

1 回答 1

3

您在后台@testing-library/react-hooks调用是绝对正确setTimeout的,您可以通过以下方式确认:

jest.useFakeTimers()
renderHook(() => {})
expect(setTimeout).toHaveBeenCalledTimes(1)

您最好关注callback被调用的次数,而不是setTimeout

afterEach(() => {
  jest.clearAllMocks();
  jest.useRealTimers();
});

describe("Test scenarios for 'useInterval' hook", () => {
  it("Should call 'callback' immediately", () => {
    jest.useFakeTimers()

    const callback = jest.fn()
    const interval = 10000
    const params = { callback, interval }
    renderHook(() => useInterval(params))

    expect(callback).toHaveBeenCalledTimes(1)
  })

  it("Should call 'callback' repeatedly", () => {
    jest.useFakeTimers()

    const callback = jest.fn()
    const interval = 10000
    const params = { callback, interval }
    renderHook(() => useInterval(params))

    jest.advanceTimersByTime(interval * 2)

    // Expect 3 calls: 1 immediate call and 2 interval calls:
    expect(callback).toHaveBeenCalledTimes(3)
  })
})
于 2020-11-04T12:27:51.210 回答