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

如何在React的useEffect中mock setTimeout以通过组件测试

解决ForgotPassword组件延迟状态测试失败问题

核心问题分析

你遇到的问题本质是React的state更新、useEffect执行与Jest fake timers的联动逻辑没走对:输入框触发state更新后,useEffect会重新创建setTimeout,但如果没有正确触发React的渲染周期+定时器快进,测试就捕获不到延迟后的状态变化。

修复步骤&代码示例

1. 组件侧确认依赖正确

先确保你的ForgotPassword组件中,useEffect的依赖数组包含输入状态(比如email),否则输入变化不会重新触发timeout:

// 组件内的useEffect示例
useEffect(() => {
  const timer = setTimeout(() => {
    // 验证邮箱逻辑,更新emailValidity、btnDisable
    const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
    setEmailValidity(isValid);
    setBtnDisable(!isValid);
  }, 1300);

  return () => clearTimeout(timer); // 清理旧定时器,避免内存泄漏
}, [email]); // 必须依赖email,输入变化才会重置定时器

2. 测试侧调整定时器逻辑

放弃单独用runOnlyPendingTimers,改用jest.advanceTimersByTime(),并且所有触发状态更新、定时器快进的操作都要遵循React的渲染周期(React Testing Library的fireEvent/userEvent已封装act,但定时器操作需手动处理):

import { render, screen, fireEvent, act } from '@testing-library/react';
import ForgotPassword from './ForgotPassword';

test('输入有效邮箱后,1300ms后按钮解除禁用', () => {
  // 1. 启用fake timers,必须在渲染组件前调用
  jest.useFakeTimers();
  render(<ForgotPassword />);

  const emailInput = screen.getByLabelText('邮箱');
  const submitBtn = screen.getByRole('button', { name: '发送验证码' });

  // 2. 输入有效邮箱,触发state更新
  fireEvent.change(emailInput, { target: { value: 'test@example.com' } });

  // 3. 快进1300ms,确保React处理所有延迟状态更新
  act(() => {
    jest.advanceTimersByTime(1300);
  });

  // 4. 断言按钮状态
  expect(submitBtn).not.toBeDisabled();

  // 5. 测试结束恢复真实定时器,避免影响其他用例
  jest.useRealTimers();
});

常见坑点排查

  • 不要在渲染组件后才调用jest.useFakeTimers(),否则组件初始化时的setTimeout不会被fake timers捕获。
  • 必须清理旧定时器:组件内的useEffect一定要返回清理函数,避免输入多次时多个定时器叠加导致状态混乱。
  • 如果用userEvent代替fireEvent,注意userEvent.type是异步操作,需要await后再操作定时器:
    await userEvent.type(emailInput, 'test@example.com');
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 11:05:05