运行时包管理器打包器测试运行器代码检查格式化指南参考博客反馈

运行时行为

了解 Bun test 的运行时集成、环境变量、超时和错误处理

bun test 与 Bun 的运行时深度集成。此集成是 bun test 能够快速运行的原因之一。

环境变量

NODE_ENV

bun test 会将 $NODE_ENV 设置为 "test",除非它已经在环境或 .env 文件中设置。大多数测试运行器也会这样做。

test.ts
import { test, expect } from "bun:test";

test("NODE_ENV 被设置为 test", () => {
  expect(process.env.NODE_ENV).toBe("test");
});

你可以通过显式设置 NODE_ENV 来覆盖默认值:

terminal
NODE_ENV=development bun test

TZ(时区)

除非 TZ 环境变量对其进行覆盖,否则 bun test 会使用 UTC(Etc/UTC)作为时区。这可以确保日期和时间行为在不同机器上保持一致。

test.ts
import { test, expect } from "bun:test";

test("默认时区为 UTC", () => {
  const date = new Date();
  expect(date.getTimezoneOffset()).toBe(0);
});

要使用特定时区进行测试:

terminal
TZ=America/New_York bun test

测试超时

每个测试的默认超时时间为 5000 毫秒(5 秒)。超出该时间的测试将失败。

全局超时

通过 --timeout 选项修改全局超时:

terminal
bun test --timeout 10000  # 10 秒

每个测试的超时

将每个测试的超时时间作为测试函数的第三个参数:

test.ts
import { test, expect } from "bun:test";

test("快速测试", () => {
  expect(1 + 1).toBe(2);
}, 1000); // 1 秒超时

test("慢速测试", async () => {
  await new Promise(resolve => setTimeout(resolve, 8000));
}, 10000); // 10 秒超时

无限超时

使用 0 或 Infinity 可禁用超时:

test.ts
test("无超时测试", async () => {
  // 该测试可以无限运行
  await someVeryLongOperation();
}, 0);

错误处理

未捕获的错误

bun test 会跟踪未处理的 Promise 拒绝以及测试之间发生的错误。如果发生任何此类情况,即使没有测试失败,bun test 也会以非零退出码退出。在下面两个示例中,错误都发生在加载文件时,因此完全不会运行该文件中的测试。

这有助于捕获异步代码中的错误,否则可能被忽略:

test.ts
import { test, expect } from "bun:test";

test("test 1", () => {
  expect(true).toBe(true);
});

// This error happens outside any test
queueMicrotask(() => {
  throw new Error("Unhandled error");
});

test("test 2", () => {
  expect(true).toBe(true);
});

// bun test reports this as "Unhandled error between tests", does not run
// this file's tests (0 pass, 1 error), and exits with code 1

Promise 拒绝

测试运行器也会捕获未处理的 Promise 拒绝:

test.ts
import { test, expect } from "bun:test";

test("test 1", () => {
  expect(1).toBe(1);
});

// bun test reports this as "Unhandled error between tests", does not run
// this file's tests, and exits with code 1
Promise.reject(new Error("Unhandled rejection"));

自定义错误处理

你可以在测试设置中配置自定义错误处理器:

test-setup.ts
process.on("uncaughtException", error => {
  console.error("未捕获异常:", error);
  process.exit(1);
});

process.on("unhandledRejection", (reason, promise) => {
  console.error("未处理的拒绝发生于:", promise, "原因:", reason);
  process.exit(1);
});

CLI 选项集成

多个 Bun CLI 标志也适用于 bun test:

内存使用

terminal
# 降低测试运行时 VM 的内存使用
bun test --smol

调试

terminal
# 将调试器附加到测试运行进程
bun test --inspect
bun test --inspect-brk

模块加载

terminal
# 在测试文件之前运行脚本(适用于全局初始化/模拟)
bun test --preload ./setup.ts

# 设置编译时常量
bun test --define "process.env.API_URL='http://localhost:3000'"

# 将文件扩展名映射到内置加载器
bun test --loader .svg:text

# 使用不同的 tsconfig
bun test --tsconfig-override ./test-tsconfig.json

# 设置 package.json 中的条件以控制模块解析
bun test --conditions development

# 为测试加载环境变量
bun test --env-file .env.test

相关安装选项

bash
# 影响测试执行期间的网络请求或自动安装
bun test --prefer-offline
bun test --frozen-lockfile

监视和热重载

监视模式

使用 --watch 标志时,测试运行器会监视文件更改并重新运行测试。

terminal
bun test --watch

热重载

--hot 标志与之类似,但会更积极地在运行之间保留状态:

terminal
bun test --hot

对于大多数测试,请使用 --watch:它能在运行之间提供更好的隔离。

全局变量

以下全局变量在测试文件中无需导入即可使用:

test.ts
// 以下变量均全局可用
test("全局 test 函数", () => {
  expect(true).toBe(true);
});

describe("全局 describe", () => {
  beforeAll(() => {
    // 全局 beforeAll
  });

  it("全局 it 函数", () => {
    // it 是 test 的别名
  });
});

// Jest 兼容
jest.fn();

// Vitest 兼容
vi.fn();

你也可以显式导入它们:

test.ts
import { test, it, describe, expect, beforeAll, beforeEach, afterAll, afterEach, jest, vi } from "bun:test";

进程集成

退出码

bun test 使用标准退出码:

  • 0: 所有测试通过,没有未处理的错误
  • 1: 发生测试失败或未处理的错误

信号处理

测试运行器处理常见信号:

terminal
# 优雅地停止测试执行
kill -SIGTERM <测试进程 PID>

# 立即停止测试执行
kill -SIGKILL <测试进程 PID>

环境检测

Bun 会自动检测某些环境并调整行为:

test.ts
// GitHub Actions 检测
if (process.env.GITHUB_ACTIONS) {
  // Bun 自动发送 GitHub Actions 注释
}

// CI 检测
if (process.env.CI) {
  // 可能针对 CI 环境调整某些行为
}

性能考虑

单进程

测试运行器默认在单个进程中运行所有测试。这带来了:

  • 启动更快:无需启动多个进程
  • 共享内存:高效利用资源
  • 调试更简单:所有测试都在一个进程中运行

但这意味着:

  • 测试共享全局状态(使用生命周期钩子进行清理)
  • 一个测试崩溃可能影响其他测试
  • 不支持真正的单个测试并行

内存管理

terminal
# 监控内存使用
bun test --smol  # 降低内存占用

# 对于大型测试套件,考虑按文件拆分
bun test src/unit/
bun test src/integration/

测试隔离

由于测试运行在同一进程,需确保适当清理:

test.ts
import { afterEach, jest } from "bun:test";

afterEach(() => {
  // 清理全局状态
  global.myGlobalVar = undefined;
  delete process.env.TEST_VAR;

  // 如果需要,恢复模拟函数
  jest.restoreAllMocks();
});