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

React Native中jest.useFakeTimers在beforeEach内外调用差异问题

概述

测试过程中发现,jest.useFakeTimers 在 beforeEach 钩子内部调用、与在钩子外部调用时,存在不一致的运行行为。

可复现示例
// Foo.tsx
import * as React from 'react';
import {View} from 'react-native';

const Foo: React.FC = () => {
  const [timePassed, setTimePassed] = React.useState<number>(0);
  const [flag, setFlag] = React.useState(true);

  console.log(`timePassed: ${timePassed}, flag: ${flag}`);
  React.useEffect(() => {
    if (flag) {
      setTimeout(() => {
        if (timePassed > 1) {
          setFlag(false);
        }
        setTimePassed(timePassed + 1);
      }, 1);
    }
  }, [flag, timePassed]);

  return <View />;
};

export default Foo;
// foo.outside.test.tsx
import * as React from 'react';
import {render} from '@testing-library/react-native';
import Foo from '../Foo';

jest.useFakeTimers();

describe('Test Foo', () => {
  test('1', () => {
    render(<Foo />);
    jest.runAllTimers();
  });

  test('2', () => {
    render(<Foo />);
    jest.runAllTimers();
  });
});
// foo.inside.test.tsx
import * as React from 'react';
import {render} from '@testing-library/react-native';
import Foo from '../Foo';

describe('Test Foo', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

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

  test('1', () => {
    render(<Foo />);
    jest.runAllTimers();
  });

  test('2', () => {
    render(<Foo />);
    jest.runAllTimers();
  });
});
测试结果

外部调用场景

$ npx jest __tests__/foo.outside.test.tsx
  console.log
    timePassed: 0, flag: true

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 1, flag: true

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 2, flag: true

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 2, flag: false

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 3, flag: false

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 0, flag: true

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 1, flag: true

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 2, flag: true

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 2, flag: false

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 3, flag: false

      at log (Foo.tsx:8:11)

 PASS  __tests__/foo.outside.test.tsx
  Test Foo
    ✓ 1 (58 ms)
    ✓ 2 (21 ms)

内部调用场景

$ npx jest __tests__/foo.inside.test.tsx
  console.log
    timePassed: 0, flag: true

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 1, flag: true

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 2, flag: true

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 2, flag: false

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 3, flag: false

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 0, flag: true

      at log (Foo.tsx:8:11)

  console.log
    timePassed: 1, flag: true

      at log (Foo.tsx:8:11)

 PASS  __tests__/foo.inside.test.tsx
  Test Foo
    ✓ 1 (62 ms)
    ✓ 2 (14 ms)
现象观察

经测试可见:外部调用配置下,两个测试用例中的 jest.runAllTimers 均可将定时器完整执行到终止状态;但内部调用配置下,仅第一个测试用例的定时器执行到了终止状态,第二个测试用例的定时器提前退出,未完整执行。

问题原因

两种写法的行为差异,核心是afterEach中的清理逻辑和Jest假定时器、React调度器的工作机制不匹配导致的:

  • 全局外部调用jest.useFakeTimers()时,假定时器在整个文件测试周期内持续生效,两个用例之间不会切换回真实定时器,也没有额外的提前执行逻辑。每个用例调用jest.runAllTimers()时,会递归执行所有已注册、以及执行过程中新产生的定时任务,直到队列完全清空,所以两个用例的定时器都能跑到终止状态。
  • 写在beforeEach里的场景问题出在afterEach的逻辑:
    1. 第一个用例执行完后,jest.runOnlyPendingTimers()只会执行调用瞬间已经在队列里的定时任务,不会处理任务执行过程中新注册的定时器,跑完这一步就直接切回真实定时器,第一个用例中React触发的后续状态更新、连带新的定时任务没有被清空,残留在了运行环境中。
    2. 第二个用例启动时,虽然beforeEach重新开启了假定时器,但React内部的调度器、React Native测试环境的宿主对象已经缓存了上一步切回的真实定时器引用,后续组件里setTimeout注册的任务不会进入新的假定时器队列。此时调用jest.runAllTimers()只能拿到队列里最开始的两个定时任务,跑完就直接退出,表现就是定时器没有执行完整。

修复方式很简单,删掉afterEach里的jest.runOnlyPendingTimers()即可。每次调用jest.useFakeTimers()本身就会重置假定时器队列,不需要额外在清理阶段执行残留任务。如果需要主动规避用例间的状态污染,可以在beforeEach里加一句jest.clearAllTimers()主动清空队列即可。

运行环境信息
  • 操作系统:macOS Big Sur v11.6.5
  • react: 17.0.2
  • react-native: 0.65.1
  • jest: 28.1.2
  • @testing-library/react-native: 10.1.1
  • typescript: 4.5.2

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 03:49:12