Bun

编写测试

使用从内置的 bun:test 模块导入的 Jest-like API 定义测试。从长远来看,Bun 的目标是完全兼容 Jest;目前,支持 有限的 expect 匹配器集合。

基本用法

定义一个简单的测试

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

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

Jest 风格的全局变量

测试可以使用 describe 分组到套件中。

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

describe("arithmetic", () => {
  test("2 + 2", () => {
    expect(2 + 2).toBe(4);
  });

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

测试可以是 async

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

test("2 * 2", async () => {
  const result = await Promise.resolve(2 * 2);
  expect(result).toEqual(4);
});

或者,使用 done 回调来表示完成。如果您在测试定义中包含 done 回调作为参数,则必须调用它,否则测试将挂起。

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

test("2 * 2", done => {
  Promise.resolve(2 * 2).then(result => {
    expect(result).toEqual(4);
    done();
  });
});

超时

通过将数字作为第三个参数传递给 test,可选地指定每个测试的超时时间(以毫秒为单位)。

import { test } from "bun:test";

test("wat", async () => {
  const data = await slowOperation();
  expect(data).toBe(42);
}, 500); // test must run in <500ms

bun:test 中,测试超时会抛出一个不可捕获的异常,以强制测试停止运行并失败。我们还会杀死测试中生成的任何子进程,以避免留下僵尸进程在后台潜伏。

如果未通过此超时选项或 jest.setDefaultTimeout() 覆盖,则每个测试的默认超时为 5000ms(5 秒)。

🧟 僵尸进程杀手

当测试超时且通过 Bun.spawnBun.spawnSyncnode:child_process 在测试中生成的进程未被杀死时,它们将被自动杀死,并且会向控制台记录一条消息。这可以防止僵尸进程在超时测试后留在后台。

test.skip

使用 test.skip 跳过单个测试。这些测试将不会运行。

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

test.skip("wat", () => {
  // TODO: fix this
  expect(0.1 + 0.2).toEqual(0.3);
});

test.todo

使用 test.todo 将测试标记为待办事项。这些测试将不会运行。

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

test.todo("fix this", () => {
  myTestFunction();
});

要运行待办事项测试并查找通过的测试,请使用 bun test --todo

bun test --todo
my.test.ts:
✗ unimplemented feature
  ^ this test is marked as todo but passes. Remove `.todo` or check that test is correct.

 0 pass
 1 fail
 1 expect() calls

使用此标志,失败的待办事项测试不会导致错误,但通过的待办事项测试将被标记为失败,以便您可以删除待办事项标记或 修复测试。

test.only

要运行特定测试或测试套件,请使用 test.only()describe.only()

import { test, describe } from "bun:test";

test("test #1", () => {
  // does not run
});

test.only("test #2", () => {
  // runs
});

describe.only("only", () => {
  test("test #3", () => {
    // runs
  });
});

以下命令将仅执行测试 #2 和 #3。

bun test

test.if

要条件性地运行测试,请使用 test.if()。如果条件为真,则测试将运行。这对于仅应在特定架构或操作系统上运行的测试特别有用。

test.if(Math.random() > 0.5)("runs half the time", () => {
  // ...
});

const macOS = process.arch === "darwin";
test.if(macOS)("runs on macOS", () => {
  // runs if macOS
});

test.skipIf

如果要根据某些条件跳过测试,请使用 test.skipIf()describe.skipIf()

const macOS = process.arch === "darwin";

test.skipIf(macOS)("runs on non-macOS", () => {
  // runs if *not* macOS
});

test.todoIf

如果您想将测试标记为 TODO,请使用 test.todoIf()describe.todoIf()。仔细选择 skipIftodoIf 可以显示“对该目标无效”和“已计划但尚未实现”之间的区别。

const macOS = process.arch === "darwin";

// TODO: we've only implemented this for Linux so far.
test.todoIf(macOS)("runs on posix", () => {
  // runs if *not* macOS
});

test.failing

当您知道某个测试当前失败但您想跟踪它并在它开始通过时收到通知时,请使用 test.failing()。这会反转测试结果

  • 标记为 .failing() 的失败测试将通过
  • 标记为 .failing() 的通过测试将失败(并显示一条消息,指出它现在通过,应该修复)
// This will pass because the test is failing as expected
test.failing("math is broken", () => {
  expect(0.1 + 0.2).toBe(0.3); // fails due to floating point precision
});

// This will fail with a message that the test is now passing
test.failing("fixed bug", () => {
  expect(1 + 1).toBe(2); // passes, but we expected it to fail
});

这对于跟踪您计划稍后修复的已知错误或实现测试驱动开发很有用。

Describe 块的条件测试

条件修饰符 .if().skipIf().todoIf() 也可以应用于 describe 块,影响套件中的所有测试。

const isMacOS = process.platform === "darwin";

// Only runs the entire suite on macOS
describe.if(isMacOS)("macOS-specific features", () => {
  test("feature A", () => {
    // only runs on macOS
  });

  test("feature B", () => {
    // only runs on macOS
  });
});

// Skips the entire suite on Windows
describe.skipIf(process.platform === "win32")("Unix features", () => {
  test("feature C", () => {
    // skipped on Windows
  });
});

// Marks the entire suite as TODO on Linux
describe.todoIf(process.platform === "linux")("Upcoming Linux support", () => {
  test("feature D", () => {
    // marked as TODO on Linux
  });
});

test.eachdescribe.each

要使用多组数据运行相同的测试,请使用 test.each。这将创建一个参数化测试,为每个提供的测试用例运行一次。

const cases = [
  [1, 2, 3],
  [3, 4, 7],
];

test.each(cases)("%p + %p should be %p", (a, b, expected) => {
  expect(a + b).toBe(expected);
});

您还可以使用 describe.each 创建一个参数化套件,该套件为每个测试用例运行一次

describe.each([
  [1, 2, 3],
  [3, 4, 7],
])("add(%i, %i)", (a, b, expected) => {
  test(`returns ${expected}`, () => {
    expect(a + b).toBe(expected);
  });

  test(`sum is greater than each value`, () => {
    expect(a + b).toBeGreaterThan(a);
    expect(a + b).toBeGreaterThan(b);
  });
});

参数传递

参数如何传递给您的测试函数取决于您的测试用例的结构

  • 如果表行是一个数组(例如 [1, 2, 3]),则每个元素都作为单独的参数传递
  • 如果行不是数组(例如一个对象),则它作为单个参数传递
// Array items passed as individual arguments
test.each([
  [1, 2, 3],
  [4, 5, 9],
])("add(%i, %i) = %i", (a, b, expected) => {
  expect(a + b).toBe(expected);
});

// Object items passed as a single argument
test.each([
  { a: 1, b: 2, expected: 3 },
  { a: 4, b: 5, expected: 9 },
])("add($a, $b) = $expected", data => {
  expect(data.a + data.b).toBe(data.expected);
});

格式说明符

有许多选项可用于格式化测试标题

%p漂亮的格式
%s字符串
%d数字
%i整数
%f浮点数
%jJSON
%o对象
%#测试用例的索引
%%单百分号 (%)

Examples

// Basic specifiers
test.each([
  ["hello", 123],
  ["world", 456],
])("string: %s, number: %i", (str, num) => {
  // "string: hello, number: 123"
  // "string: world, number: 456"
});

// %p for pretty-format output
test.each([
  [{ name: "Alice" }, { a: 1, b: 2 }],
  [{ name: "Bob" }, { x: 5, y: 10 }],
])("user %p with data %p", (user, data) => {
  // "user { name: 'Alice' } with data { a: 1, b: 2 }"
  // "user { name: 'Bob' } with data { x: 5, y: 10 }"
});

// %# for index
test.each(["apple", "banana"])("fruit #%# is %s", fruit => {
  // "fruit #0 is apple"
  // "fruit #1 is banana"
});

断言计数

Bun 支持验证在测试期间调用了特定数量的断言

expect.hasAssertions()

使用 expect.hasAssertions() 来验证在测试期间至少调用了一个断言

test("async work calls assertions", async () => {
  expect.hasAssertions(); // Will fail if no assertions are called

  const data = await fetchData();
  expect(data).toBeDefined();
});

这对于异步测试特别有用,以确保您的断言确实运行。

expect.assertions(count)

使用 expect.assertions(count) 来验证在测试期间调用了特定数量的断言

test("exactly two assertions", () => {
  expect.assertions(2); // Will fail if not exactly 2 assertions are called

  expect(1 + 1).toBe(2);
  expect("hello").toContain("ell");
});

这有助于确保您的所有断言都运行,尤其是在具有多个代码路径的复杂异步代码中。

类型测试

Bun 包含用于测试 TypeScript 类型的 expectTypeOf,与 Vitest 兼容。

expectTypeOf

注意 — 这些函数在运行时是空操作 - 您需要单独运行 TypeScript 来验证类型检查。

expectTypeOf 函数提供由 TypeScript 的类型检查器检查的类型级别断言。重要

要测试您的类型

  1. 使用 expectTypeOf 编写您的类型断言
  2. 运行 bunx tsc --noEmit 以检查您的类型是否正确
import { expectTypeOf } from "bun:test";

// Basic type assertions
expectTypeOf<string>().toEqualTypeOf<string>();
expectTypeOf(123).toBeNumber();
expectTypeOf("hello").toBeString();

// Object type matching
expectTypeOf({ a: 1, b: "hello" }).toMatchObjectType<{ a: number }>();

// Function types
function greet(name: string): string {
  return `Hello ${name}`;
}

expectTypeOf(greet).toBeFunction();
expectTypeOf(greet).parameters.toEqualTypeOf<[string]>();
expectTypeOf(greet).returns.toEqualTypeOf<string>();

// Array types
expectTypeOf([1, 2, 3]).items.toBeNumber();

// Promise types
expectTypeOf(Promise.resolve(42)).resolves.toBeNumber();

有关 expectTypeOf 匹配器的完整文档,请参阅 API 参考

匹配器

Bun 实现了以下匹配器。完全兼容 Jest 仍在路线图上;在此处跟踪进度 这里

TypeScript 类型安全

Bun 的测试运行器通过智能类型检查为您的测试断言提供了增强的 TypeScript 支持。类型系统有助于在编译时捕获潜在的错误,同时在需要时仍然保持灵活性。

默认严格类型检查

默认情况下,Bun 的测试匹配器强制实际值和期望值之间进行严格类型检查

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

test("strict typing", () => {
  const str = "hello";
  const num = 42;

  expect(str).toBe("hello"); // ✅ OK: string to string
  expect(num).toBe(42); // ✅ OK: number to number
  expect(str).toBe(42); // ❌ TypeScript error: string vs number
});

这有助于捕获您可能意外比较不同类型值的常见错误。

使用类型参数进行宽松类型检查

有时您的测试需要更大的灵活性,尤其是在使用以下情况时

  • 来自 API 的动态数据
  • 可以返回多种类型的多态函数
  • 通用实用函数
  • 现有测试套件的迁移

对于这些情况,您可以通过向匹配器方法提供显式类型参数来“退出”严格类型检查

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

test("relaxed typing with type parameters", () => {
  const value: unknown = getSomeValue();

  // These would normally cause TypeScript errors, but type parameters allow them:
  expect(value).toBe<number>(42); // No TS error, runtime check still works
  expect(value).toEqual<string>("hello"); // No TS error, runtime check still works
  expect(value).toStrictEqual<boolean>(true); // No TS error, runtime check still works
});

test("useful for dynamic data", () => {
  const apiResponse: any = { status: "success" };

  // Without type parameter: TypeScript error (any vs string)
  // expect(apiResponse.status).toBe("success");

  // With type parameter: No TypeScript error, runtime assertion still enforced
  expect(apiResponse.status).toBe<string>("success"); // ✅ OK
});

从较宽松的类型系统迁移

如果从 TypeScript 集成较宽松的测试框架迁移,您可以使用类型参数作为垫脚石

// Old Jest test that worked but wasn't type-safe
expect(response.data).toBe(200); // No type error in some setups

// Bun equivalent with explicit typing during migration
expect(response.data).toBe<number>(200); // Explicit about expected type

// Ideal Bun test after refactoring
const statusCode: number = response.data;
expect(statusCode).toBe(200); // Type-safe without explicit parameter