typescript-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TypeScript Testing with Bun

使用Bun进行TypeScript测试

TypeScript/JavaScript-specific testing patterns and best practices using Bun's built-in test runner, complementing general testing-workflow skill.
使用Bun内置测试运行器的TypeScript/JavaScript专属测试模式与最佳实践,作为通用测试工作流技能的补充。

CRITICAL: Bun Test Execution

重要提示:Bun测试执行

NEVER use jest, vitest, or other test runners in Bun projects:
bash
undefined
绝不要在Bun项目中使用jest、vitest或其他测试运行器:
bash
undefined

✅ CORRECT - Bun test execution

✅ 正确 - Bun测试执行

bun test bun test --watch bun test src/tests bun test --coverage bun test --bail bun test tests/unit.test.ts
bun test bun test --watch bun test src/tests bun test --coverage bun test --bail bun test tests/unit.test.ts

❌ WRONG - Never use jest in Bun projects

❌ 错误 - 绝不要在Bun项目中使用jest

❌ jest

❌ jest

❌ jest --watch

❌ jest --watch

❌ npm run test (if mapped to jest)

❌ npm run test (如果映射到jest)

❌ WRONG - Never use vitest in Bun projects

❌ 错误 - 绝不要在Bun项目中使用vitest

❌ vitest

❌ vitest

❌ vitest run

❌ vitest run


**Always use `bun test` directly** (never use jest/vitest in Bun projects).

---

**始终直接使用`bun test`**(绝不要在Bun项目中使用jest/vitest)。

---

Test File Organization

测试文件组织

File Naming Conventions

文件命名规范

Bun recognizes test files by standard conventions:
src/
├── utils/
│   ├── math.ts
│   ├── math.test.ts              # ✅ Standard .test.ts
│   ├── string-utils.spec.ts      # ✅ Alternative .spec.ts
│   └── validation/
│       ├── validator.ts
│       └── validator.test.ts
├── services/
│   ├── api.ts
│   └── __tests__/                # ✅ __tests__ directory
│       └── api.test.ts
└── components/
    ├── Button.tsx
    └── Button.test.tsx           # ✅ React component tests
Bun通过标准规范识别测试文件:
src/
├── utils/
│   ├── math.ts
│   ├── math.test.ts              # ✅ 标准.test.ts格式
│   ├── string-utils.spec.ts      # ✅ 替代.spec.ts格式
│   └── validation/
│       ├── validator.ts
│       └── validator.test.ts
├── services/
│   ├── api.ts
│   └── __tests__/                # ✅ __tests__目录
│       └── api.test.ts
└── components/
    ├── Button.tsx
    └── Button.test.tsx           # ✅ React组件测试

Discovery Patterns

自动发现规则

Bun automatically finds tests matching:
  • *.test.ts
    /
    *.test.tsx
  • *.test.js
    /
    *.test.jsx
  • *.spec.ts
    /
    *.spec.tsx
  • *.spec.js
    /
    *.spec.jsx
  • Files in
    __tests__
    directories

Bun会自动匹配以下测试文件:
  • *.test.ts
    /
    *.test.tsx
  • *.test.js
    /
    *.test.jsx
  • *.spec.ts
    /
    *.spec.tsx
  • *.spec.js
    /
    *.spec.jsx
  • __tests__
    目录下的文件

Basic Test Structure

基础测试结构

Simple Test Example

简单测试示例

typescript
import { describe, it, expect } from "bun:test";
import { add, multiply } from "./math";

describe("Math utilities", () => {
  it("should add two numbers", () => {
    expect(add(2, 3)).toBe(5);
  });

  it("should multiply two numbers", () => {
    expect(multiply(4, 5)).toBe(20);
  });

  it("should handle negative numbers", () => {
    expect(add(-1, -2)).toBe(-3);
  });
});
typescript
import { describe, it, expect } from "bun:test";
import { add, multiply } from "./math";

describe("数学工具函数", () => {
  it("应该能对两个数字求和", () => {
    expect(add(2, 3)).toBe(5);
  });

  it("应该能对两个数字求积", () => {
    expect(multiply(4, 5)).toBe(20);
  });

  it("应该能处理负数", () => {
    expect(add(-1, -2)).toBe(-3);
  });
});

Describe Blocks

描述块嵌套

Organize tests with nested describe blocks:
typescript
import { describe, it, expect } from "bun:test";
import { UserService } from "./user-service";

describe("UserService", () => {
  describe("create", () => {
    it("should create user with valid data", () => {
      // Test implementation
    });

    it("should throw error on invalid email", () => {
      // Test implementation
    });
  });

  describe("update", () => {
    it("should update user properties", () => {
      // Test implementation
    });

    it("should not update protected fields", () => {
      // Test implementation
    });
  });

  describe("delete", () => {
    it("should delete user by id", () => {
      // Test implementation
    });
  });
});

使用嵌套describe块组织测试:
typescript
import { describe, it, expect } from "bun:test";
import { UserService } from "./user-service";

describe("UserService", () => {
  describe("create方法", () => {
    it("应该能使用有效数据创建用户", () => {
      // 测试实现
    });

    it("在邮箱无效时应该抛出错误", () => {
      // 测试实现
    });
  });

  describe("update方法", () => {
    it("应该能更新用户属性", () => {
      // 测试实现
    });

    it("不应该更新受保护字段", () => {
      // 测试实现
    });
  });

  describe("delete方法", () => {
    it("应该能通过ID删除用户", () => {
      // 测试实现
    });
  });
});

Bun Test API

Bun测试API

Describe and It

Describe与It

typescript
import { describe, it, expect } from "bun:test";

describe("Feature name", () => {
  it("should do something", () => {
    expect(true).toBe(true);
  });

  it("should handle edge case", () => {
    expect(() => riskyOperation()).toThrow();
  });
});
typescript
import { describe, it, expect } from "bun:test";

describe("功能名称", () => {
  it("应该完成某件事", () => {
    expect(true).toBe(true);
  });

  it("应该处理边界情况", () => {
    expect(() => riskyOperation()).toThrow();
  });
});

Common Assertions

常用断言

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

// Equality
expect(value).toBe(5);              // Strict equality (===)
expect(obj).toEqual({ a: 1 });      // Deep equality
expect(value).toStrictEqual(5);     // Strict deep equality

// Truthiness
expect(value).toBeTruthy();         // Truthy value
expect(value).toBeFalsy();          // Falsy value
expect(value).toBeNull();           // null
expect(value).toBeUndefined();      // undefined
expect(value).toBeDefined();        // Not undefined

// Numbers
expect(number).toBeGreaterThan(5);
expect(number).toBeGreaterThanOrEqual(5);
expect(number).toBeLessThan(10);
expect(number).toBeLessThanOrEqual(10);
expect(0.1 + 0.2).toBeCloseTo(0.3); // Float comparison

// Strings
expect(string).toMatch(/pattern/);
expect(string).toContain("substring");

// Arrays
expect(array).toContain(value);
expect(array).toHaveLength(3);

// Objects
expect(obj).toHaveProperty("key");
expect(obj).toHaveProperty("key", expectedValue);

// Exceptions
expect(() => throwError()).toThrow();
expect(() => throwError()).toThrow(CustomError);
expect(() => throwError()).toThrow(/error message/);
typescript
import { expect } from "bun:test";

// 相等性
expect(value).toBe(5);              // 严格相等 (===)
expect(obj).toEqual({ a: 1 });      // 深度相等
expect(value).toStrictEqual(5);     // 严格深度相等

// 真值判断
expect(value).toBeTruthy();         // 真值
expect(value).toBeFalsy();          // 假值
expect(value).toBeNull();           // null
expect(value).toBeUndefined();      // undefined
expect(value).toBeDefined();        // 非undefined

// 数字断言
expect(number).toBeGreaterThan(5);
expect(number).toBeGreaterThanOrEqual(5);
expect(number).toBeLessThan(10);
expect(number).toBeLessThanOrEqual(10);
expect(0.1 + 0.2).toBeCloseTo(0.3); // 浮点数比较

// 字符串断言
expect(string).toMatch(/pattern/);
expect(string).toContain("子字符串");

// 数组断言
expect(array).toContain(value);
expect(array).toHaveLength(3);

// 对象断言
expect(obj).toHaveProperty("key");
expect(obj).toHaveProperty("key", expectedValue);

// 异常断言
expect(() => throwError()).toThrow();
expect(() => throwError()).toThrow(CustomError);
expect(() => throwError()).toThrow(/错误信息/);

Skipping and Only

跳过与仅运行指定测试

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

describe("Feature", () => {
  it("should test this", () => {
    // Runs
  });

  it.skip("should skip this test", () => {
    // Skipped
  });

  it.only("should run only this test", () => {
    // Only this runs in the suite
  });

  describe.skip("skipped suite", () => {
    it("won't run", () => {});
  });
});
typescript
import { it, describe } from "bun:test";

describe("功能模块", () => {
  it("应该执行这个测试", () => {
    // 正常运行
  });

  it.skip("应该跳过这个测试", () => {
    // 被跳过
  });

  it.only("应该只运行这个测试", () => {
    // 在测试套件中仅运行此测试
  });

  describe.skip("被跳过的测试套件", () => {
    it("不会运行", () => {});
  });
});

Test.todo

Test.todo

typescript
import { it } from "bun:test";

it.todo("feature not yet implemented");
it.todo("edge case to handle");

typescript
import { it } from "bun:test";

it.todo("尚未实现的功能");
it.todo("需要处理的边界情况");

Setup and Teardown

测试前置与清理

beforeEach and afterEach

beforeEach与afterEach

typescript
import { describe, it, beforeEach, afterEach, expect } from "bun:test";
import { Database } from "./database";

describe("Database operations", () => {
  let db: Database;

  beforeEach(() => {
    // Setup before each test
    db = new Database(":memory:");
    db.initialize();
  });

  afterEach(() => {
    // Cleanup after each test
    db.close();
  });

  it("should insert and retrieve data", () => {
    db.insert("users", { id: 1, name: "John" });
    const user = db.query("SELECT * FROM users WHERE id = 1");
    expect(user.name).toBe("John");
  });
});
typescript
import { describe, it, beforeEach, afterEach, expect } from "bun:test";
import { Database } from "./database";

describe("数据库操作", () => {
  let db: Database;

  beforeEach(() => {
    // 每个测试前的前置操作
    db = new Database(":memory:");
    db.initialize();
  });

  afterEach(() => {
    // 每个测试后的清理操作
    db.close();
  });

  it("应该能插入并检索数据", () => {
    db.insert("users", { id: 1, name: "John" });
    const user = db.query("SELECT * FROM users WHERE id = 1");
    expect(user.name).toBe("John");
  });
});

beforeAll and afterAll

beforeAll与afterAll

typescript
import { describe, it, beforeAll, afterAll, expect } from "bun:test";
import { setupExpensiveResource } from "./resources";

describe("Resource-intensive operations", () => {
  let resource: any;

  beforeAll(() => {
    // Setup once for entire suite
    resource = setupExpensiveResource();
  });

  afterAll(() => {
    // Cleanup once after entire suite
    resource.teardown();
  });

  it("uses expensive resource", () => {
    expect(resource.isReady()).toBe(true);
  });

  it("performs operation", () => {
    const result = resource.process("data");
    expect(result).toBeDefined();
  });
});
typescript
import { describe, it, beforeAll, afterAll, expect } from "bun:test";
import { setupExpensiveResource } from "./resources";

describe("资源密集型操作", () => {
  let resource: any;

  beforeAll(() => {
    // 整个测试套件仅执行一次前置操作
    resource = setupExpensiveResource();
  });

  afterAll(() => {
    // 整个测试套件完成后仅执行一次清理
    resource.teardown();
  });

  it("使用昂贵资源", () => {
    expect(resource.isReady()).toBe(true);
  });

  it("执行操作", () => {
    const result = resource.process("data");
    expect(result).toBeDefined();
  });
});

Nested Hooks

嵌套钩子函数

typescript
import { describe, it, beforeEach } from "bun:test";

describe("Outer suite", () => {
  let value = 0;

  beforeEach(() => {
    value = 10;
  });

  it("test in outer", () => {
    expect(value).toBe(10);
  });

  describe("Inner suite", () => {
    beforeEach(() => {
      value *= 2;  // Runs after outer beforeEach
    });

    it("test in inner", () => {
      expect(value).toBe(20);  // 10 * 2
    });
  });
});

typescript
import { describe, it, beforeEach } from "bun:test";

describe("外层测试套件", () => {
  let value = 0;

  beforeEach(() => {
    value = 10;
  });

  it("外层测试", () => {
    expect(value).toBe(10);
  });

  describe("内层测试套件", () => {
    beforeEach(() => {
      value *= 2;  // 在外层beforeEach之后运行
    });

    it("内层测试", () => {
      expect(value).toBe(20);  // 10 * 2
    });
  });
});

Mocking with Bun

使用Bun进行Mock

Using mock()

使用mock()

typescript
import { mock } from "bun:test";
import { fetchUser } from "./api";

const mockFetch = mock((userId: string) => {
  return { id: userId, name: "Mock User" };
});

// Test mock behavior
const result = mockFetch("123");
expect(result.name).toBe("Mock User");
expect(mockFetch.mock.calls.length).toBe(1);
expect(mockFetch.mock.calls[0]).toEqual(["123"]);
typescript
import { mock } from "bun:test";
import { fetchUser } from "./api";

const mockFetch = mock((userId: string) => {
  return { id: userId, name: "模拟用户" };
});

// 测试mock行为
const result = mockFetch("123");
expect(result.name).toBe("模拟用户");
expect(mockFetch.mock.calls.length).toBe(1);
expect(mockFetch.mock.calls[0]).toEqual(["123"]);

Mock Objects and Modules

Mock对象与模块

typescript
import { describe, it, expect, mock } from "bun:test";

describe("Service with mocked dependency", () => {
  it("should use mocked database", () => {
    const mockDb = {
      query: mock((sql: string) => [{ id: 1, name: "Test" }]),
      close: mock(() => {}),
    };

    const service = new Service(mockDb);
    const result = service.getUser(1);

    expect(result.name).toBe("Test");
    expect(mockDb.query.mock.calls.length).toBe(1);
  });
});
typescript
import { describe, it, expect, mock } from "bun:test";

describe("依赖被Mock的服务", () => {
  it("应该使用Mock数据库", () => {
    const mockDb = {
      query: mock((sql: string) => [{ id: 1, name: "测试用户" }]),
      close: mock(() => {}),
    };

    const service = new Service(mockDb);
    const result = service.getUser(1);

    expect(result.name).toBe("测试用户");
    expect(mockDb.query.mock.calls.length).toBe(1);
  });
});

Module Mocking

模块Mock

typescript
import { describe, it, expect, mock } from "bun:test";
import { getUserFromAPI } from "./api";

// Mock entire modules
mock.module("./api", () => ({
  getUserFromAPI: mock((id: string) => ({
    id,
    name: "Mocked User",
  })),
}));

describe("API integration", () => {
  it("should work with mocked API", async () => {
    const user = await getUserFromAPI("123");
    expect(user.name).toBe("Mocked User");
  });
});
typescript
import { describe, it, expect, mock } from "bun:test";
import { getUserFromAPI } from "./api";

// Mock整个模块
mock.module("./api", () => ({
  getUserFromAPI: mock((id: string) => ({
    id,
    name: "Mocked User",
  })),
}));

describe("API集成测试", () => {
  it("应该与Mock API正常协作", async () => {
    const user = await getUserFromAPI("123");
    expect(user.name).toBe("Mocked User");
  });
});

Spy on Function Calls

函数调用Spy

typescript
import { describe, it, expect, mock } from "bun:test";

describe("Spy on calls", () => {
  it("should track function calls", () => {
    const originalFunc = (x: number) => x * 2;
    const spied = mock(originalFunc);

    const result1 = spied(5);
    const result2 = spied(10);

    expect(result1).toBe(10);
    expect(result2).toBe(20);
    expect(spied.mock.calls.length).toBe(2);
    expect(spied.mock.results[0].value).toBe(10);
    expect(spied.mock.results[1].value).toBe(20);
  });
});
typescript
import { describe, it, expect, mock } from "bun:test";

describe("Spy函数调用", () => {
  it("应该追踪函数调用", () => {
    const originalFunc = (x: number) => x * 2;
    const spied = mock(originalFunc);

    const result1 = spied(5);
    const result2 = spied(10);

    expect(result1).toBe(10);
    expect(result2).toBe(20);
    expect(spied.mock.calls.length).toBe(2);
    expect(spied.mock.results[0].value).toBe(10);
    expect(spied.mock.results[1].value).toBe(20);
  });
});

Mock Return Values

Mock返回值

typescript
import { describe, it, expect, mock } from "bun:test";

describe("Mock return values", () => {
  it("should return configured values", () => {
    const mockFunc = mock();

    // Set return values for specific calls
    mockFunc.mock.returns = [
      { value: "first" },
      { value: "second" },
      { value: "third" },
    ];

    expect(mockFunc()).toEqual({ value: "first" });
    expect(mockFunc()).toEqual({ value: "second" });
  });

  it("should throw errors when configured", () => {
    const errorMock = mock(() => {
      throw new Error("Mocked error");
    });

    expect(() => errorMock()).toThrow("Mocked error");
  });
});

typescript
import { describe, it, expect, mock } from "bun:test";

describe("Mock返回值", () => {
  it("应该返回配置好的值", () => {
    const mockFunc = mock();

    // 为特定调用设置返回值
    mockFunc.mock.returns = [
      { value: "第一次返回" },
      { value: "第二次返回" },
      { value: "第三次返回" },
    ];

    expect(mockFunc()).toEqual({ value: "第一次返回" });
    expect(mockFunc()).toEqual({ value: "第二次返回" });
  });

  it("在配置后应该抛出错误", () => {
    const errorMock = mock(() => {
      throw new Error("Mocked error");
    });

    expect(() => errorMock()).toThrow("Mocked error");
  });
});

Async Testing

异步测试

Async/Await in Tests

测试中的Async/Await

typescript
import { describe, it, expect } from "bun:test";
import { fetchUser } from "./api";

describe("Async operations", () => {
  it("should fetch user data", async () => {
    const user = await fetchUser("123");
    expect(user.id).toBe("123");
    expect(user.name).toBeDefined();
  });

  it("should handle fetch errors", async () => {
    expect(fetchUser("invalid")).rejects.toThrow();
  });
});
typescript
import { describe, it, expect } from "bun:test";
import { fetchUser } from "./api";

describe("异步操作", () => {
  it("应该能获取用户数据", async () => {
    const user = await fetchUser("123");
    expect(user.id).toBe("123");
    expect(user.name).toBeDefined();
  });

  it("应该能处理获取错误", async () => {
    expect(fetchUser("invalid")).rejects.toThrow();
  });
});

Promise Testing

Promise测试

typescript
import { describe, it, expect } from "bun:test";

describe("Promise handling", () => {
  it("should resolve with data", () => {
    const promise = Promise.resolve({ id: 1, name: "User" });
    return expect(promise).resolves.toEqual({ id: 1, name: "User" });
  });

  it("should reject with error", () => {
    const promise = Promise.reject(new Error("Failed"));
    return expect(promise).rejects.toThrow("Failed");
  });
});
typescript
import { describe, it, expect } from "bun:test";

describe("Promise处理", () => {
  it("应该能解析并返回数据", () => {
    const promise = Promise.resolve({ id: 1, name: "用户" });
    return expect(promise).resolves.toEqual({ id: 1, name: "用户" });
  });

  it("应该能拒绝并返回错误", () => {
    const promise = Promise.reject(new Error("失败"));
    return expect(promise).rejects.toThrow("失败");
  });
});

Concurrent Async Tests

并发异步测试

typescript
import { describe, it, expect } from "bun:test";

describe("Concurrent operations", () => {
  it("should handle multiple concurrent requests", async () => {
    const results = await Promise.all([
      fetchData("1"),
      fetchData("2"),
      fetchData("3"),
    ]);

    expect(results).toHaveLength(3);
    expect(results[0].id).toBe("1");
    expect(results[1].id).toBe("2");
    expect(results[2].id).toBe("3");
  });

  it("should race multiple promises", async () => {
    const winner = await Promise.race([
      slowOperation(100),
      slowOperation(50),
      slowOperation(200),
    ]);

    expect(winner).toBeDefined();
  });
});

typescript
import { describe, it, expect } from "bun:test";

describe("并发操作", () => {
  it("应该能处理多个并发请求", async () => {
    const results = await Promise.all([
      fetchData("1"),
      fetchData("2"),
      fetchData("3"),
    ]);

    expect(results).toHaveLength(3);
    expect(results[0].id).toBe("1");
    expect(results[1].id).toBe("2");
    expect(results[2].id).toBe("3");
  });

  it("应该能在多个Promise中竞速", async () => {
    const winner = await Promise.race([
      slowOperation(100),
      slowOperation(50),
      slowOperation(200),
    ]);

    expect(winner).toBeDefined();
  });
});

Test Fixtures and Utilities

测试夹具与工具

Shared Test Data

共享测试数据

typescript
// tests/fixtures/users.ts
export const testUsers = {
  admin: {
    id: "1",
    email: "admin@example.com",
    role: "admin",
  },
  user: {
    id: "2",
    email: "user@example.com",
    role: "user",
  },
  guest: {
    id: "3",
    email: "guest@example.com",
    role: "guest",
  },
};

export const invalidUsers = {
  noEmail: { id: "4" },
  invalidEmail: { id: "5", email: "not-an-email" },
  noId: { email: "test@example.com" },
};

// In test file
import { describe, it, expect } from "bun:test";
import { testUsers } from "./fixtures/users";

describe("User roles", () => {
  it("should verify admin role", () => {
    expect(testUsers.admin.role).toBe("admin");
  });
});
typescript
// tests/fixtures/users.ts
export const testUsers = {
  admin: {
    id: "1",
    email: "admin@example.com",
    role: "admin",
  },
  user: {
    id: "2",
    email: "user@example.com",
    role: "user",
  },
  guest: {
    id: "3",
    email: "guest@example.com",
    role: "guest",
  },
};

export const invalidUsers = {
  noEmail: { id: "4" },
  invalidEmail: { id: "5", email: "not-an-email" },
  noId: { email: "test@example.com" },
};

// 在测试文件中
import { describe, it, expect } from "bun:test";
import { testUsers } from "./fixtures/users";

describe("用户角色", () => {
  it("应该验证管理员角色", () => {
    expect(testUsers.admin.role).toBe("admin");
  });
});

Fixture Setup Function

夹具设置函数

typescript
// tests/fixtures/setup.ts
export function createMockUser(overrides: Partial<User> = {}): User {
  return {
    id: "test-id",
    email: "test@example.com",
    name: "Test User",
    role: "user",
    createdAt: new Date(),
    ...overrides,
  };
}

export function createMockDatabase() {
  const users: User[] = [];

  return {
    addUser: (user: User) => {
      users.push(user);
      return user;
    },
    getUser: (id: string) => users.find(u => u.id === id),
    getAllUsers: () => [...users],
    clear: () => users.splice(0),
  };
}

// In test
import { describe, it, beforeEach, expect } from "bun:test";
import { createMockUser, createMockDatabase } from "./fixtures/setup";

describe("User repository", () => {
  let db: ReturnType<typeof createMockDatabase>;

  beforeEach(() => {
    db = createMockDatabase();
  });

  it("should add and retrieve users", () => {
    const user = createMockUser({ name: "John Doe" });
    db.addUser(user);

    expect(db.getUser(user.id)?.name).toBe("John Doe");
  });
});

typescript
// tests/fixtures/setup.ts
export function createMockUser(overrides: Partial<User> = {}): User {
  return {
    id: "test-id",
    email: "test@example.com",
    name: "测试用户",
    role: "user",
    createdAt: new Date(),
    ...overrides,
  };
}

export function createMockDatabase() {
  const users: User[] = [];

  return {
    addUser: (user: User) => {
      users.push(user);
      return user;
    },
    getUser: (id: string) => users.find(u => u.id === id),
    getAllUsers: () => [...users],
    clear: () => users.splice(0),
  };
}

// 在测试中
import { describe, it, beforeEach, expect } from "bun:test";
import { createMockUser, createMockDatabase } from "./fixtures/setup";

describe("用户仓库", () => {
  let db: ReturnType<typeof createMockDatabase>;

  beforeEach(() => {
    db = createMockDatabase();
  });

  it("应该能添加并检索用户", () => {
    const user = createMockUser({ name: "John Doe" });
    db.addUser(user);

    expect(db.getUser(user.id)?.name).toBe("John Doe");
  });
});

Coverage with Bun

使用Bun生成覆盖率报告

Running Coverage

运行覆盖率测试

bash
undefined
bash
undefined

Generate coverage report

生成覆盖率报告

bun test --coverage
bun test --coverage

Coverage with specific files

针对特定文件生成覆盖率

bun test --coverage src/
bun test --coverage src/

HTML coverage report

生成HTML格式覆盖率报告

bun test --coverage --coverage-html
undefined
bun test --coverage --coverage-html
undefined

Configuration in bunfig.toml

在bunfig.toml中配置

toml
[test]
toml
[test]

Enable coverage

启用覆盖率

coverage = true
coverage = true

Coverage reporting format

覆盖率报告格式

coverageFormat = ["text", "html", "json"]
coverageFormat = ["text", "html", "json"]

Files to report on

覆盖率阈值

coverageThreshold = 80
coverageThreshold = 80

Exclude from coverage

排除覆盖率统计的文件

coverageIgnore = ["/node_modules/", "/dist/"]
coverageIgnore = ["/node_modules/", "/dist/"]

Root directory for coverage

覆盖率统计的根目录

coverageRoot = "src"
undefined
coverageRoot = "src"
undefined

Coverage Reports

覆盖率报告输出

bash
undefined
bash
undefined

Text report

文本格式报告

bun test --coverage
bun test --coverage

Generate HTML report in coverage/

在coverage/目录生成HTML报告

bun test --coverage --coverage-html
bun test --coverage --coverage-html

JSON report for CI/CD

生成JSON格式报告用于CI/CD

bun test --coverage coverage/coverage.json

---
bun test --coverage coverage/coverage.json

---

Integration Testing

集成测试

Testing HTTP APIs

HTTP API测试

typescript
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
import { startServer, stopServer } from "./server";

describe("API Integration", () => {
  let baseUrl: string;

  beforeAll(async () => {
    const server = await startServer();
    baseUrl = `http://localhost:${server.port}`;
  });

  afterAll(async () => {
    await stopServer();
  });

  it("should create a user", async () => {
    const response = await fetch(`${baseUrl}/api/users`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email: "test@example.com" }),
    });

    expect(response.status).toBe(201);
    const data = await response.json();
    expect(data.id).toBeDefined();
  });

  it("should retrieve user", async () => {
    const response = await fetch(`${baseUrl}/api/users/1`);
    expect(response.status).toBe(200);
  });
});
typescript
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
import { startServer, stopServer } from "./server";

describe("API集成测试", () => {
  let baseUrl: string;

  beforeAll(async () => {
    const server = await startServer();
    baseUrl = `http://localhost:${server.port}`;
  });

  afterAll(async () => {
    await stopServer();
  });

  it("应该能创建用户", async () => {
    const response = await fetch(`${baseUrl}/api/users`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email: "test@example.com" }),
    });

    expect(response.status).toBe(201);
    const data = await response.json();
    expect(data.id).toBeDefined();
  });

  it("应该能检索用户", async () => {
    const response = await fetch(`${baseUrl}/api/users/1`);
    expect(response.status).toBe(200);
  });
});

Database Integration

数据库集成测试

typescript
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
import { Database } from "./database";

describe("Database operations", () => {
  let db: Database;

  beforeAll(async () => {
    db = new Database(":memory:");
    await db.initialize();
    await db.runMigrations();
  });

  afterAll(async () => {
    await db.close();
  });

  it("should perform CRUD operations", async () => {
    // Create
    const user = await db.users.create({
      email: "test@example.com",
      name: "Test User",
    });
    expect(user.id).toBeDefined();

    // Read
    const retrieved = await db.users.findById(user.id);
    expect(retrieved.email).toBe("test@example.com");

    // Update
    await db.users.update(user.id, { name: "Updated" });
    const updated = await db.users.findById(user.id);
    expect(updated.name).toBe("Updated");

    // Delete
    await db.users.delete(user.id);
    const deleted = await db.users.findById(user.id);
    expect(deleted).toBeNull();
  });
});

typescript
import { describe, it, expect, beforeAll, afterAll } from "bun:test";
import { Database } from "./database";

describe("数据库操作", () => {
  let db: Database;

  beforeAll(async () => {
    db = new Database(":memory:");
    await db.initialize();
    await db.runMigrations();
  });

  afterAll(async () => {
    await db.close();
  });

  it("应该能执行CRUD操作", async () => {
    // 创建
    const user = await db.users.create({
      email: "test@example.com",
      name: "测试用户",
    });
    expect(user.id).toBeDefined();

    // 读取
    const retrieved = await db.users.findById(user.id);
    expect(retrieved.email).toBe("test@example.com");

    // 更新
    await db.users.update(user.id, { name: "已更新" });
    const updated = await db.users.findById(user.id);
    expect(updated.name).toBe("已更新");

    // 删除
    await db.users.delete(user.id);
    const deleted = await db.users.findById(user.id);
    expect(deleted).toBeNull();
  });
});

Testing TypeScript Types

TypeScript类型测试

Type Testing with TypeScript

使用TypeScript进行类型测试

typescript
import { describe, it, expectTypeOf } from "bun:test";
import { processUser } from "./user-processor";

describe("Type safety", () => {
  it("should have correct return type", () => {
    const result = processUser({ name: "John", age: 30 });

    // Check type at compile time
    expectTypeOf(result).toMatchTypeOf<{ success: boolean }>();
  });

  it("should enforce parameter types", () => {
    // TypeScript will catch these at compile time
    // @ts-expect-error - wrong type
    processUser({ name: 123 });

    // @ts-expect-error - missing required field
    processUser({ age: 30 });
  });
});

typescript
import { describe, it, expectTypeOf } from "bun:test";
import { processUser } from "./user-processor";

describe("类型安全", () => {
  it("应该有正确的返回类型", () => {
    const result = processUser({ name: "John", age: 30 });

    // 在编译时检查类型
    expectTypeOf(result).toMatchTypeOf<{ success: boolean }>();
  });

  it("应该强制参数类型", () => {
    // TypeScript会在编译时捕获这些错误
    // @ts-expect-error - 错误类型
    processUser({ name: 123 });

    // @ts-expect-error - 缺少必填字段
    processUser({ age: 30 });
  });
});

Configuration in bunfig.toml

bunfig.toml中的配置

Complete Test Configuration

完整测试配置

toml
[test]
toml
[test]

Test file patterns

测试文件匹配规则

root = "." prefix = "" suffix = [".test", ".spec"] testNamePattern = ""
root = "." prefix = "" suffix = [".test", ".spec"] testNamePattern = ""

Coverage

覆盖率配置

coverage = true coverageFormat = ["text", "html", "json"] coverageThreshold = 80 coverageRoot = "src" coverageIgnore = ["/node_modules/"]
coverage = true coverageFormat = ["text", "html", "json"] coverageThreshold = 80 coverageRoot = "src" coverageIgnore = ["/node_modules/"]

Test execution

测试执行配置

bail = false timeout = 30000 reportFailures = true
bail = false timeout = 30000 reportFailures = true

Reporters

报告器

reporters = ["spec"] # or ["tap", "junit"]
reporters = ["spec"] # 或 ["tap", "junit"]

Output

输出配置

preloadModules = []
undefined
preloadModules = []
undefined

With npm scripts in package.json

在package.json中配置npm脚本

json
{
  "scripts": {
    "test": "bun test",
    "test:watch": "bun test --watch",
    "test:coverage": "bun test --coverage",
    "test:ui": "bun test --coverage --coverage-html",
    "test:single": "bun test tests/unit.test.ts",
    "test:bail": "bun test --bail",
    "test:debug": "bun test --inspect-brk"
  }
}

json
{
  "scripts": {
    "test": "bun test",
    "test:watch": "bun test --watch",
    "test:coverage": "bun test --coverage",
    "test:ui": "bun test --coverage --coverage-html",
    "test:single": "bun test tests/unit.test.ts",
    "test:bail": "bun test --bail",
    "test:debug": "bun test --inspect-brk"
  }
}

React Component Testing

React组件测试

Testing React Components

测试React组件

typescript
import { describe, it, expect } from "bun:test";
import { render, screen } from "bun:test:dom";
import { Button } from "./Button";

describe("Button component", () => {
  it("should render button with text", () => {
    render(<Button label="Click me" />);

    const button = screen.getByRole("button", { name: "Click me" });
    expect(button).toBeDefined();
  });

  it("should call onClick handler", async () => {
    const handleClick = mock();
    render(<Button label="Click" onClick={handleClick} />);

    const button = screen.getByRole("button");
    button.click();

    expect(handleClick.mock.calls.length).toBe(1);
  });

  it("should disable button when disabled prop is true", () => {
    render(<Button label="Disabled" disabled={true} />);

    const button = screen.getByRole("button") as HTMLButtonElement;
    expect(button.disabled).toBe(true);
  });
});

typescript
import { describe, it, expect } from "bun:test";
import { render, screen } from "bun:test:dom";
import { Button } from "./Button";

describe("Button组件", () => {
  it("应该渲染带文本的按钮", () => {
    render(<Button label="点击我" />);

    const button = screen.getByRole("button", { name: "点击我" });
    expect(button).toBeDefined();
  });

  it("应该调用onClick处理函数", async () => {
    const handleClick = mock();
    render(<Button label="点击" onClick={handleClick} />);

    const button = screen.getByRole("button");
    button.click();

    expect(handleClick.mock.calls.length).toBe(1);
  });

  it("当disabled属性为true时应该禁用按钮", () => {
    render(<Button label="已禁用" disabled={true} />);

    const button = screen.getByRole("button") as HTMLButtonElement;
    expect(button.disabled).toBe(true);
  });
});

Common Testing Patterns

常用测试模式

Arrange-Act-Assert

准备-执行-断言模式

typescript
import { describe, it, expect } from "bun:test";
import { calculateTotal } from "./calculator";

describe("calculateTotal", () => {
  it("should sum array of numbers", () => {
    // Arrange - Set up test data
    const items = [
      { price: 10, quantity: 2 },
      { price: 5, quantity: 3 },
    ];

    // Act - Execute functionality
    const total = calculateTotal(items);

    // Assert - Verify results
    expect(total).toBe(35);  // (10*2) + (5*3)
  });
});
typescript
import { describe, it, expect } from "bun:test";
import { calculateTotal } from "./calculator";

describe("calculateTotal函数", () => {
  it("应该对数字数组求和", () => {
    // 准备 - 设置测试数据
    const items = [
      { price: 10, quantity: 2 },
      { price: 5, quantity: 3 },
    ];

    // 执行 - 调用功能函数
    const total = calculateTotal(items);

    // 断言 - 验证结果
    expect(total).toBe(35);  // (10*2) + (5*3)
  });
});

Testing Error Conditions

错误条件测试

typescript
import { describe, it, expect } from "bun:test";
import { validateEmail } from "./validators";

describe("validateEmail", () => {
  it("should validate correct email", () => {
    expect(validateEmail("test@example.com")).toBe(true);
  });

  it("should reject invalid emails", () => {
    expect(validateEmail("not-an-email")).toBe(false);
    expect(validateEmail("@example.com")).toBe(false);
    expect(validateEmail("test@")).toBe(false);
  });

  it("should throw on null input", () => {
    expect(() => validateEmail(null as any)).toThrow();
  });
});
typescript
import { describe, it, expect } from "bun:test";
import { validateEmail } from "./validators";

describe("validateEmail函数", () => {
  it("应该验证正确的邮箱", () => {
    expect(validateEmail("test@example.com")).toBe(true);
  });

  it("应该拒绝无效邮箱", () => {
    expect(validateEmail("not-an-email")).toBe(false);
    expect(validateEmail("@example.com")).toBe(false);
    expect(validateEmail("test@")).toBe(false);
  });

  it("当输入为null时应该抛出错误", () => {
    expect(() => validateEmail(null as any)).toThrow();
  });
});

Testing Class Methods

类方法测试

typescript
import { describe, it, expect, beforeEach } from "bun:test";
import { Counter } from "./counter";

describe("Counter class", () => {
  let counter: Counter;

  beforeEach(() => {
    counter = new Counter();
  });

  it("should increment", () => {
    counter.increment();
    expect(counter.value).toBe(1);
  });

  it("should decrement", () => {
    counter.increment();
    counter.decrement();
    expect(counter.value).toBe(0);
  });

  it("should reset to zero", () => {
    counter.increment();
    counter.increment();
    counter.reset();
    expect(counter.value).toBe(0);
  });
});

typescript
import { describe, it, expect, beforeEach } from "bun:test";
import { Counter } from "./counter";

describe("Counter类", () => {
  let counter: Counter;

  beforeEach(() => {
    counter = new Counter();
  });

  it("应该能自增", () => {
    counter.increment();
    expect(counter.value).toBe(1);
  });

  it("应该能自减", () => {
    counter.increment();
    counter.decrement();
    expect(counter.value).toBe(0);
  });

  it("应该能重置为零", () => {
    counter.increment();
    counter.increment();
    counter.reset();
    expect(counter.value).toBe(0);
  });
});

Edge Case Testing

边界情况测试

Common Edge Cases for TypeScript

TypeScript常用边界情况

typescript
import { describe, it, expect } from "bun:test";
import { processArray } from "./processor";

describe("processArray edge cases", () => {
  it("should handle empty array", () => {
    expect(processArray([])).toEqual([]);
  });

  it("should handle single item", () => {
    expect(processArray([1])).toEqual([1]);
  });

  it("should handle undefined values", () => {
    const result = processArray([1, undefined, 3]);
    expect(result).toContain(1);
    expect(result).toContain(3);
  });

  it("should handle null values", () => {
    const result = processArray([1, null, 3]);
    expect(result.length).toBeLessThanOrEqual(3);
  });

  it("should handle very large numbers", () => {
    const large = Number.MAX_SAFE_INTEGER;
    expect(processArray([large, large])).toBeDefined();
  });

  it("should handle special values", () => {
    expect(processArray([0, -0, NaN])).toBeDefined();
  });
});
typescript
import { describe, it, expect } from "bun:test";
import { processArray } from "./processor";

describe("processArray函数边界情况", () => {
  it("应该能处理空数组", () => {
    expect(processArray([])).toEqual([]);
  });

  it("应该能处理单个元素", () => {
    expect(processArray([1])).toEqual([1]);
  });

  it("应该能处理undefined值", () => {
    const result = processArray([1, undefined, 3]);
    expect(result).toContain(1);
    expect(result).toContain(3);
  });

  it("应该能处理null值", () => {
    const result = processArray([1, null, 3]);
    expect(result.length).toBeLessThanOrEqual(3);
  });

  it("应该能处理极大数字", () => {
    const large = Number.MAX_SAFE_INTEGER;
    expect(processArray([large, large])).toBeDefined();
  });

  it("应该能处理特殊值", () => {
    expect(processArray([0, -0, NaN])).toBeDefined();
  });
});

Null/Undefined Handling

Null/Undefined处理

typescript
import { describe, it, expect } from "bun:test";
import { getUser } from "./user-service";

describe("Null/undefined handling", () => {
  it("should return null for missing user", async () => {
    const user = await getUser("nonexistent");
    expect(user).toBeNull();
  });

  it("should handle undefined optional fields", async () => {
    const user = await getUser("123");
    if (user) {
      expect(user.middleName).toBeUndefined();
    }
  });

  it("should distinguish null from undefined", () => {
    const nullValue = null;
    const undefinedValue = undefined;

    expect(nullValue).toBeNull();
    expect(undefinedValue).toBeUndefined();
    expect(nullValue).not.toBe(undefinedValue);
  });
});

typescript
import { describe, it, expect } from "bun:test";
import { getUser } from "./user-service";

describe("Null/undefined处理", () => {
  it("对于不存在的用户应该返回null", async () => {
    const user = await getUser("nonexistent");
    expect(user).toBeNull();
  });

  it("应该能处理undefined可选字段", async () => {
    const user = await getUser("123");
    if (user) {
      expect(user.middleName).toBeUndefined();
    }
  });

  it("应该区分null和undefined", () => {
    const nullValue = null;
    const undefinedValue = undefined;

    expect(nullValue).toBeNull();
    expect(undefinedValue).toBeUndefined();
    expect(nullValue).not.toBe(undefinedValue);
  });
});

Zero-Warnings Policy

零警告策略

Treat Warnings as Errors

将警告视为错误

Running tests should produce zero warnings:
bash
undefined
运行测试时应该产生零警告:
bash
undefined

Run tests and fail on any warnings

运行测试并在出现任何警告时失败

bun test
bun test

If warnings appear, identify and fix them

如果出现警告,定位并修复它们

Common causes:

常见原因:

- Deprecated API usage

- 使用已废弃的API

- Unhandled promise rejections

- 未处理的Promise拒绝

- Memory leaks in tests

- 测试中的内存泄漏

- Resource cleanup issues

- 资源清理问题

undefined
undefined

Configuration for Warnings

警告相关配置

toml
[test]
toml
[test]

Fail on warnings (if available in your Bun version)

出现警告时失败(如果你的Bun版本支持)

reportFailures = true
reportFailures = true

Configure reporters to show warnings

配置报告器以显示警告

reporters = ["spec"]
undefined
reporters = ["spec"]
undefined

Handling Expected Warnings

处理预期警告

If a library produces unavoidable warnings:
typescript
import { describe, it, expect } from "bun:test";

describe("Feature with expected warning", () => {
  it("should work despite library warning", () => {
    // This test runs code that produces a library warning
    // Document why the warning is acceptable
    expect(unsafeLibraryFunction()).toBeDefined();
  });
});

如果某个库产生不可避免的警告:
typescript
import { describe, it, expect } from "bun:test";

describe("存在预期警告的功能", () => {
  it("即使有库警告也应该正常工作", () => {
    // 此测试运行的代码会产生库警告
    // 记录为什么该警告是可接受的
    expect(unsafeLibraryFunction()).toBeDefined();
  });
});

Makefile Integration

Makefile集成

Test Targets

测试目标

makefile
.PHONY: test test-watch test-coverage test-single test-bail
makefile
.PHONY: test test-watch test-coverage test-single test-bail

Run all tests

运行所有测试

test: bun test
test: bun test

Watch mode - rerun on file changes

监听模式 - 文件变化时重新运行测试

test-watch: bun test --watch
test-watch: bun test --watch

Run with coverage

运行测试并生成覆盖率报告

test-coverage: bun test --coverage
test-coverage: bun test --coverage

View HTML coverage report

查看HTML格式覆盖率报告

test-ui: bun test --coverage --coverage-html @echo "Coverage report: coverage/index.html"
test-ui: bun test --coverage --coverage-html @echo "覆盖率报告地址:coverage/index.html"

Run single test file

运行单个测试文件

test-single: bun test tests/specific.test.ts
test-single: bun test tests/specific.test.ts

Fail on first error

遇到第一个错误时立即失败

test-bail: bun test --bail
test-bail: bun test --bail

Debug tests

调试测试

test-debug: bun test --inspect-brk
test-debug: bun test --inspect-brk

Full test suite with checks

完整测试套件检查

check: test lint type-check @echo "All checks passed!"

---
check: test lint type-check @echo "所有检查通过!"

---

Project Structure Patterns

项目结构模式

Organized Test Structure

有序的测试结构

src/
├── utils/
│   ├── math.ts
│   ├── math.test.ts         # Colocated with source
│   └── string.ts
├── services/
│   ├── api.ts
│   └── api.test.ts
├── __tests__/               # Alternative: centralized tests
│   ├── fixtures/
│   │   ├── users.ts
│   │   └── setup.ts
│   ├── unit/
│   │   └── math.test.ts
│   ├── integration/
│   │   └── api.test.ts
│   └── e2e/
│       └── workflow.test.ts
└── index.ts
src/
├── utils/
│   ├── math.ts
│   ├── math.test.ts         # 与源代码同目录
│   └── string.ts
├── services/
│   ├── api.ts
│   └── api.test.ts
├── __tests__/               # 替代方案:集中式测试目录
│   ├── fixtures/
│   │   ├── users.ts
│   │   └── setup.ts
│   ├── unit/
│   │   └── math.test.ts
│   ├── integration/
│   │   └── api.test.ts
│   └── e2e/
│       └── workflow.test.ts
└── index.ts

Test Fixtures Directory

测试夹具目录

tests/
├── fixtures/
│   ├── users.ts            # Test user data
│   ├── database.ts         # Test database setup
│   ├── api-responses.ts    # Mock API responses
│   └── setup.ts            # Fixture functions
└── helpers/
    ├── assertions.ts       # Custom assertions
    └── mocks.ts           # Mock utilities

tests/
├── fixtures/
│   ├── users.ts            # 测试用户数据
│   ├── database.ts         # 测试数据库设置
│   ├── api-responses.ts    # Mock API响应
│   └── setup.ts            # 夹具函数
└── helpers/
    ├── assertions.ts       # 自定义断言
    └── mocks.ts           # Mock工具

Dependency Installation

依赖安装

Adding Test Dependencies

添加测试依赖

bash
undefined
bash
undefined

Core testing (already built-in with Bun)

核心测试功能(Bun已内置)

No installation needed - use "bun:test"

无需安装 - 直接使用"bun:test"

Additional testing utilities (optional)

额外测试工具(可选)

bun add --dev @types/bun
bun add --dev @types/bun

React testing (if using React)

React测试(如果使用React)

bun add --dev jsdom
bun add --dev jsdom

HTTP testing utilities

HTTP测试工具

bun add --dev node-fetch @types/node
bun add --dev node-fetch @types/node

Test data generation

测试数据生成

bun add --dev faker

---

**Note:** For general testing principles and strategies not specific to TypeScript/JavaScript, see the testing-workflow skill.
bun add --dev faker

---

**注意:** 对于不特定于TypeScript/JavaScript的通用测试原则和策略,请参考testing-workflow技能文档。