@testing-library/react-hooks 调用 setTimeout 两次
@testing-library/react-hooks calls setTimeout two times
这是我自定义的 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
。做错了什么?有什么想法吗?
您完全正确,@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)
})
})
这是我自定义的 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
。做错了什么?有什么想法吗?
您完全正确,@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)
})
})