Bun Test 不支持时间快进?我用这招让单元测试提速 8 倍!

假设我们的组件或函数内使用了 setInterval (setTimeout 同理),如果不对其进行任何操作,"傻傻等待"的话,一个单元测试可能就要耗费几秒钟才能完成,甚至还可能超时。

老牌的 jest 或新的 vitest 以及 node:test 原生都支持"时间快进"。但是 bun:test(v1.2.23 2025-10-10)尚未支持 jest/vi.advanceTimersByTime,将报错:

js 复制代码
// 快进1秒,触发1次 setInterval 回调
37 |   vi.advanceTimersByTime(1000)
          ^
TypeError: vi.advanceTimersByTime is not a function. (In 'vi.advanceTimersByTime(1000)', 'vi.advanceTimersByTime' is undefined)

难道只能"坐以待毙"?不,我们还有一种workaround。就是通过重写或者说 mock 全局 setInterval / setTimeout

效果一睹为快:

解决办法:重写 setInterval

ts 复制代码
// src\DeepThinkButton\demo\advanced.test.tsx:
import { afterEach, beforeEach } from 'vitest'

// 1. 保存原始的 setInterval
const originalSetInterval = globalThis.setInterval

beforeEach(() => {
  // 在每个测试开始前启用假定时器
  // @ts-expect-error
  globalThis.setInterval = (callback, delay) => {
    // console.log('callback, delay:', { callback, delay })

    if (callback.name === 'deepThinkButtonInterval') {
      // 返回一个原始 ID,并且内部调用原始函数
      return originalSetInterval(() => {
        callback()
      }, 0)
    } else {
      return originalSetInterval(callback, delay)
    }
  }
})

afterEach(() => {
  // 在每个测试结束后恢复真定时器
  globalThis.setInterval = originalSetInterval
})

使用

假设我们有如下待测试代码,测试深度思考功能:

ts 复制代码
// 逐个字符显示,当完整内容显示后停止
timer = setInterval(function deepThinkButtonInterval() {
  setThinkContent((text) => {
    if (text.length < THINK_CONTENT.length) {
      // 每次输出 N 个字符
      return THINK_CONTENT.slice(0, text.length + 2)
    }

    clearInterval(timer)
    setThinkingStatus('Completed')
    setThinkingStopTime(Date.now())
    return text
  })
}, 20)

执行

ts 复制代码
❯ bun test src/DeepThinkButton/

正常情况耗时 2s+:

ts 复制代码
✓ 自定义 icon、按钮尺寸,以及思考内容折叠后效果 [2563.00ms]

第一步修改待测试代码:

diff 复制代码
- timer = setInterval(() => {
+ timer = setInterval(function deepThinkButtonInterval() {

也就是匿名函数改成具名函数,方便我们针对性 mock。

当我们给单测添加上述 mock 后,耗时减少到 1s+

ts 复制代码
✓ 自定义 icon、按钮尺寸,以及思考内容折叠后效果 [1344.00ms]

但其实我们还可以加速,即在一个 interval 回调中多次运行函数:

ts 复制代码
// src\DeepThinkButton\demo\advanced.test.tsx:
return originalSetInterval(() => {
    // 运行 6 次
    callback()
    callback()
    callback()
    callback()
    callback()
    callback()
  }, 0)

可以看到又从 1s+ 优化到了 300ms+ ⚡️ !

ts 复制代码
✓ 自定义 icon、按钮尺寸,以及思考内容折叠后效果 [328.00ms]

成功将耗时从 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 s + 2s+ </math>2s+ 优化到 <math xmlns="http://www.w3.org/1998/Math/MathML"> 300 m s + 300ms+ </math>300ms+,是其 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 / 8 1/8 </math>1/8!

重构

目前只有一个 test case 还好,如果多个 case 需要则要避免违背 DRY 原则,所以接下来我们封装成函数。

ts 复制代码
// tests/utils.ts
import { afterEach, beforeEach } from 'vitest'

export function advanceInterval(
  callbackName: string,
  { ms = 0, batchCalledCount = 1 }: { ms?: number; batchCalledCount?: number } = {},
) {
  // 1. 保存原始的 setInterval
  const originalSetInterval = globalThis.setInterval

  beforeEach(() => {
    // 在每个测试开始前启用假定时器
    // @ts-expect-error
    globalThis.setInterval = (callback, delay) => {
      // 返回一个模拟的ID,或者调用原始函数
      if (callback.name === callbackName) {
        return originalSetInterval(() => {
          for (let i = 0; i < batchCalledCount; i++) {
            callback()
          }
        }, ms)
      } else {
        return originalSetInterval(callback, delay)
      }
    }
  })

  afterEach(() => {
    // 在每个测试结束后恢复真定时器
    globalThis.setInterval = originalSetInterval
  })
}

使用:

diff 复制代码
// src/DeepThinkButton/demo/advanced.test.tsx
import React from 'react'
import { test, expect } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'

import Demo from './advanced'
import { advanceInterval } from '@/tests/rtl'

+ advanceInterval('deepThinkButtonInterval', { batchCalledCount: 6 })

test('自定义 icon、按钮尺寸,以及思考内容折叠后效果', async () => {
  ...
})
相关推荐
Dr_哈哈3 小时前
LangChain Tools —— 让 AI 拥有「双手」
langchain·node.js·ai编程
Dr_哈哈4 小时前
LangChain Chain & Pipe 知识点详解
langchain·node.js·ai编程
PetterHillWater4 小时前
AI浏览器Comet用户体验测试
aigc·测试
j***29484 小时前
如何在Windows系统上安装和配置Node.js及Node版本管理器(nvm)
windows·node.js
进击的野人4 小时前
Node.js文件系统(fs模块)深度解析与实践应用
后端·正则表达式·node.js
z***3355 小时前
使用Node.js搭配express框架快速构建后端业务接口模块Demo
node.js·express
iFlow_AI6 小时前
AI 驱动的代码审查与测试用例生成:iFlow CLI在提测阶段的应用实践
prompt·测试用例·测试·心流·iflow·iflowcli
2***65637 小时前
windows下安装并使用node.js
windows·node.js
z***D6489 小时前
Windows 上彻底卸载 Node.js
windows·node.js