It also covers coverage thresholds, deterministic test isolation, and the rule to never mix bun:test and vitest imports in one package, or run bun test when the configured runner is actually Vitest.
Out of scope: framework-specific testing (React components → react-expert's react-testing, Laravel → laravel-testing) and browser E2E suites are not covered.
TypeScript Testing
Pick the right runner, then write tests with a shared Jest-compatible API.
Agent Workflow (MANDATORY)
Before ANY implementation, spawn 3 agents in parallel, one Agent call each with a name:
- fuse-ai-pilot:explore-codebase - Detect existing runner, config, test layout
- fuse-ai-pilot:research-expert - Verify latest bun test / Vitest docs via Context7/Exa
- mcp__context7__query-docs - Check mock, coverage, config APIs
After implementation, run fuse-ai-pilot:sniper for validation.
Overview
Both runners share a Jest-like API (describe/it/expect, lifecycle hooks,
snapshots, mocks). They differ on speed, coverage maturity, and CI scaling.
| Runner | Strength | Weakness |
|---|---|---|
bun test |
Fastest cold start, zero-config TS/JSX, built-in | Single process, experimental coverage, mock limits |
| Vitest | V8/Istanbul coverage, multi-worker CI scaling, ~Jest parity, browser mode | Needs Vite + config, slower cold start |
Critical Rules
- One runner per package - Never mix
bun:testandvitestimports bun run test, notbun test, when the runner is Vitest - Else Bun runs its own- Explicit coverage thresholds - Fail CI below target, don't just report
- Deterministic tests - Isolate shared state; use
--randomize(Bun) to catch order bugs - Mock at boundaries - Network/FS/time, never internal implementation detail
Decision Guide
Choosing a runner?
├── Greenfield, Bun runtime, fast local TDD → bun test
├── Large suite / heavy CI parallelism → Vitest (multi-worker)
├── Migrating from Jest / need full coverage → Vitest (V8 + Istanbul)
├── Component/DOM in real browser → Vitest browser mode (Playwright)
└── Zero-dependency script or CLI → bun test
→ See references/choosing-runner.md for the full matrix
Reference Guide
Concepts
| Topic | Reference | Load when |
|---|---|---|
| Runner selection | references/choosing-runner.md |
Deciding bun test vs Vitest |
| Bun test runner | references/bun-test.md |
Using bun test |
| Vitest | references/vitest.md |
Using Vitest |
| Shared API | references/common-patterns.md |
Writing describe/it/mock/snapshot |
Templates
| Template | Use Case |
|---|---|
references/templates/bun-setup.md |
bunfig.toml + first Bun tests |
references/templates/vitest-setup.md |
vitest.config.ts + coverage + CI |
Quick Start
Bun
import { test, expect } from "bun:test";
test("2 + 2", () => {
expect(2 + 2).toBe(4);
});
bun test --coverage
→ See references/templates/bun-setup.md
Vitest
import { test, expect } from "vitest";
test("adds 1 + 2", () => {
expect(1 + 2).toBe(3);
});
npx vitest run --coverage
→ See references/templates/vitest-setup.md
Best Practices
DO
- Colocate tests as
*.test.tsnext to source - Assert inside every
waitFor/async block - Set per-test timeouts for network-bound tests
DON'T
- Mix both runners in one package
- Rely on test execution order
- Ship experimental Bun coverage as the sole quality gate on huge suites