close

Fake timers

当你的代码中设置了很长的定时器(timeout),而你又不想在测试中等待它们时,fake timers 会非常有用。

Rstest 提供了一些实用函数,基于 @sinonjs/fake-timers 实现定时器的模拟。

rs.useFakeTimers

  • 别名: rstest.useFakeTimers
  • 类型: (config?: FakeTimerInstallOpts) => Rstest & Disposable

调用此方法可以启用定时器的模拟。底层使用 @sinonjs/fake-timers

rs.useFakeTimers();

你也可以传递配置对象以自定义 fake timers 的行为。

该配置会透传给 @sinonjs/fake-timers。例如,toFaketoNotFake 用于控制要模拟哪些定时器 API,now 用于设置初始的模拟系统时间,shouldAdvanceTime / advanceTimeDelta 用于开启自动推进。

Disposable / using 语法

Rstest 支持使用 using 语法在代码块退出时自动恢复真实定时器:

{
  using _timers = rs.useFakeTimers();
  // Fake timers 在这个代码块中启用。
}

rs.useRealTimers

  • 别名: rstest.useRealTimers
  • 类型: () => Rstest

恢复原生的定时器函数(如 setTimeoutsetInterval 等),关闭 fake timers。

rs.useRealTimers();

rs.isFakeTimers

  • 别名: rstest.isFakeTimers
  • 类型: () => boolean

如果当前启用了 fake timers,则返回 true,否则返回 false

if (rs.isFakeTimers()) {
  // Fake timers 已启用
}

rs.setSystemTime

  • 别名: rstest.setSystemTime
  • 类型: (now?: number | Date | { epochMilliseconds: number }) => Rstest

设置 fake timers 使用的当前系统时间。适用于需要测试依赖当前日期或时间的代码。

now 也可以是类似 Temporal 的对象,例如 Temporal.InstantTemporal.ZonedDateTime

rs.useFakeTimers();
rs.setSystemTime(new Date('2020-01-01T00:00:00Z'));

rs.getRealSystemTime

  • 别名: rstest.getRealSystemTime
  • 类型: () => number

即使在启用 fake timers 时,也可以返回真实系统时间(时间戳)。

const realTime = rs.getRealSystemTime();

rs.runAllTicks

  • 别名: rstest.runAllTicks
  • 类型: () => Rstest

运行所有已排队的微任务(如 process.nextTick)。

rs.runAllTimers

  • 别名: rstest.runAllTimers
  • 类型: () => Rstest

执行所有待运行的定时器(包括 timeout 和 interval)。

rs.runAllTimersAsync

  • 别名: rstest.runAllTimersAsync
  • 类型: () => Promise<Rstest>

异步执行所有待运行的定时器。

rs.runOnlyPendingTimers

  • 别名: rstest.runOnlyPendingTimers
  • 类型: () => Rstest

只运行当前待运行的定时器(不会调度新的定时器)。

rs.runOnlyPendingTimersAsync

  • 别名: rstest.runOnlyPendingTimersAsync
  • 类型: () => Promise<Rstest>

异步只运行当前待运行的定时器。

rs.advanceTimersByTime

  • 别名: rstest.advanceTimersByTime
  • 类型: (ms: number | string | Temporal.Duration) => Rstest

将 fake timers 快进指定的毫秒数,并执行在此期间计划的所有定时器。

字符串时长使用与 @sinonjs/fake-timers 相同的格式,例如 '00:10' 表示 10 秒。

rs.advanceTimersByTimeAsync

  • 别名: rstest.advanceTimersByTimeAsync
  • 类型: (ms: number | string | Temporal.Duration) => Promise<Rstest>

异步快进 fake timers 指定的毫秒数。

rs.advanceTimersToNextTimer

  • 别名: rstest.advanceTimersToNextTimer
  • 类型: (steps?: number) => Rstest

将定时器推进到下一个计划的定时器,可选地指定推进的步数。

rs.advanceTimersToNextTimerAsync

  • 别名: rstest.advanceTimersToNextTimerAsync
  • 类型: (steps?: number) => Promise<Rstest>

异步将定时器推进到下一个计划的定时器。

rs.advanceTimersToNextFrame

  • 别名: rstest.advanceTimersToNextFrame
  • 类型: () => Rstest

将定时器推进到下一个动画帧。

rs.jumpTimersByTime

  • 别名: rstest.jumpTimersByTime
  • 类型: (ms: number | string | Temporal.Duration) => Rstest

将模拟时钟直接跳过指定时间,每个受影响的定时器回调最多只会触发一次。它适用于模拟 JavaScript 引擎被挂起后恢复的场景,此时中间的 interval tick 会被跳过。

rs.useFakeTimers({ now: 0 });

const cb = rs.fn();
setInterval(cb, 1000);

rs.jumpTimersByTime(5000);

expect(cb).toHaveBeenCalledTimes(1);

rs.setTickMode

  • 别名: rstest.setTickMode
  • 类型: (mode: { mode: 'manual' | 'nextAsync' } | { mode: 'interval'; delta?: number }) => Rstest

配置 fake timers 自动推进时间的方式。

  • { mode: 'manual' }:只有调用 rs.advanceTimersByTime() 等 API 时,定时器才会推进。
  • { mode: 'nextAsync' }:时钟会持续推进到下一个计划的定时器,并在定时器之间让 Promise 回调运行。
  • { mode: 'interval', delta?: number }:按间隔推进模拟时间,等价于 shouldAdvanceTime / advanceTimeDelta
rs.useFakeTimers();
rs.setTickMode({ mode: 'nextAsync' });

await new Promise((resolve) => setTimeout(resolve, 1000));

rs.getTimerCount

  • 别名: rstest.getTimerCount
  • 类型: () => number

返回当前 fake timers 中还剩多少个待运行的定时器。

const count = rs.getTimerCount();

rs.clearAllTimers

  • 别名: rstest.clearAllTimers
  • 类型: () => Rstest

移除所有已计划但尚未执行的定时器。

rs.clearAllTimers();