You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jest/Vitest测试中更新React Prop?Timer组件测试问题

计时器组件测试问题:gameStatus更新无效的原因及解决方法

问题背景

组件代码

function Timer({ gameStatus }: TimerProps): JSX.Element {
  useEffect(() => {
    let interval;
    if (gameStatus) {
      interval = setInterval(() => setTimer((num) => num + 1), 1000);
    } else {
      if (interval) clearInterval(interval);
    }
  }, [gameStatus]);

  const [timer, setTimer] = useState(0);
  return <div role="timer">{convertToMMSSFormat(timer)}</div>;
}

测试代码及报错

已验证gameStatus为true时计时器能正常启动,但测试停止逻辑时失败,测试代码如下:

it('stops counting when the game is over', () => {
let gameStatus = true;
const { getByRole } = render(<Timer gameStatus={gameStatus} />);
act(() => vi.advanceTimersByTime(2000));
gameStatus = false;
act(() => vi.advanceTimersByTime(2000));
const timer = getByRole('timer');
expect(timer).toHaveTextContent('00:02');
expect(timer).not.toHaveTextContent('00:04');
});

报错信息:

Error: expect(element).toHaveTextContent()
Expected element to have text content:
00:02
Received:
00:04

问题:手动修改gameStatus = false无法停止计数器,想知道原因及测试中更新Props的有效方法。


问题原因

  • React组件的Props更新需要触发组件重新渲染,你测试中只是修改了普通JS变量gameStatus的值,并没有通知React重新渲染Timer组件。
  • 组件内的useEffect依赖gameStatus,只有当组件接收到新的gameStatus Props时,才会重新执行该钩子的逻辑。你修改变量后组件没重新渲染,所以useEffect不会执行清理定时器的代码,计时器自然停不下来。

解决方法

1. 测试中用rerender更新Props

React Testing Library的render函数会返回rerender方法,用它可以重新渲染组件并传递新的Props。修改后的测试代码如下:

it('stops counting when the game is over', () => {
  let gameStatus = true;
  const { getByRole, rerender } = render(<Timer gameStatus={gameStatus} />);
  
  // 快进2秒,验证计时器走到00:02
  act(() => vi.advanceTimersByTime(2000));
  expect(getByRole('timer')).toHaveTextContent('00:02');
  
  // 更新gameStatus并重新渲染组件
  gameStatus = false;
  rerender(<Timer gameStatus={gameStatus} />);
  
  // 再快进2秒,验证计时器不再增长
  act(() => vi.advanceTimersByTime(2000));
  expect(getByRole('timer')).toHaveTextContent('00:02');
  expect(getByRole('timer')).not.toHaveTextContent('00:04');
});

2. 修复组件内的定时器引用问题

原组件还有个隐藏问题:useEffect里的interval是局部变量,当gameStatus变为false时,这个变量是undefined,根本无法清理定时器。应该用useRef来保存定时器ID,确保清理时能拿到正确的引用:

function Timer({ gameStatus }: TimerProps): JSX.Element {
  const [timer, setTimer] = useState(0);
  const intervalRef = useRef<NodeJS.Timeout | null>(null);

  useEffect(() => {
    if (gameStatus) {
      // 把定时器ID存在ref里
      intervalRef.current = setInterval(() => setTimer((num) => num + 1), 1000);
    } else {
      // 从ref里取ID清理定时器
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
        intervalRef.current = null;
      }
    }
    
    // 组件卸载时也要清理定时器,防止内存泄漏
    return () => {
      if (intervalRef.current) clearInterval(intervalRef.current);
    };
  }, [gameStatus]);

  return <div role="timer">{convertToMMSSFormat(timer)}</div>;
}

内容的提问来源于stack exchange,提问作者ElMoscaviador

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 19:23:27