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 () => {
  ...
})
相关推荐
Less^_^3 小时前
使用 Node.js 开发 Telegram Bot 完整指南
ai·node.js
防火墙在线12 小时前
前后端通信加解密(Web Crypto API )
前端·vue.js·网络协议·node.js·express
霍格沃兹_测试17 小时前
软件测试 | 测试开发 | 专项测试技术初识Hook
测试
水冗水孚17 小时前
效能工具(九)之编写nodejs脚本使用get-video-duration批量读取视频时长,并生成sql语句修复数据库表字段值
sql·node.js
huangql52018 小时前
Vite与Webpack完全指南:从零开始理解前端构建工具
前端·webpack·node.js
api_1800790546018 小时前
【技术教程】Python/Node.js 调用拼多多商品详情 API 示例详解
大数据·开发语言·python·数据挖掘·node.js
大话性能18 小时前
【Pycharm 必学技巧 】以列为单位的块编辑
测试
用户47949283569151 天前
你知道node背后的libuv是什么吗
node.js
pixle01 天前
从零学习Node.js框架Koa 【一】 Koa 初探从环境搭建到第一个应用程序
前端·node.js·web·koa.js·web全栈·node服务端框架