摘要/TLDR
- 在下面的测试规范代码中,如何根据 React 测试最佳实践成功实现
setTimeout()
函数内部的异步逻辑?
// This test always returns true because the assertions wrapped inside setTimeout() never fire.
it('is clear this code does not work, so how can I implement the same "logic" correctly?', () => {
const timer = screen.getByTestId('a timer that counts all positive integers from 0 to +Infinity once a user clicks on the DOM.')
fireEvent.click(timer)
setTimeout(() => {
// I want the test runner to wait at least 1 second before making the assertion that the component incremented from 0 to 1.
const time = Number(timer.textContent)
expect(time).toBeGreaterThan(0)
}, 1100)
})
说明
- 我正在 React 中构建一个带有计时器的组件。
- 我想编写一个测试,断言计时器在组件中的一个单元格启动时开始计数
点击。
- 每次启动新游戏时,
gameProgress
都会重置。默认情况下,它设置为 newGame
,但当用户点击 <Cell/>
时,游戏状态会通过 InProgress
更新为 React.Context
。
问题
- 包裹在
setTimeout()
中的代码永远不会运行。我相信 Jest 会忽略此块中的代码。
- 我假设我需要实现一些其他方法,或者是来自 React 测试库的异步方法,例如
waitFor()
,或者可能是使用 jest Timer Mocks。但是,我尝试了这两种想法,但都没有奏效。
测试代码
// TopPanel.test.tsx
it('should start counting if the user clicks on a blank cell', () => {
render(<TopPanel/>)
// the <Cell/> component is handled elsewhere in the ReactDOM engine
const cell = screen.getByTestId('0-0')
fireEvent.click(cell)
// I want to assert that the timer hits 1 after 1100 milliseconds; I only count seconds as integers.
setTimeout(() => {
expect(time).toBeGreaterThan(0)
}, 1100)
})
组件代码
// TopPanel.tsx
export default function TopPanel() {
const { gameProgress } = useGameContext()
const [time, setTime] = React.useState(0)
useEffect(() => {
if (gameProgress === GameProgress.NewGame) setTime(0)
if (gameProgress === GameProgress.InProgress) {
const interval = setInterval(() => setTime(time + 1), 1000)
return () => clearInterval(interval)
}
})
return (
<>
<div data-testid='topPanel'>
<div>Timer: <span data-testid='timer'>{time}</span></div>
</div>
</>
)
}