- name
- typescript-testing
- description
- TypeScript/JavaScript testing practices with Bun's test runner. Activate when working with bun test, .test.ts, .test.js, .spec.ts, .spec.js, testing TypeScript/JavaScript, bunfig.toml, testing configuration, or test-related tasks in Bun projects.
# TypeScript Testing with Bun
TypeScript/JavaScript-specific testing patterns and best practices using Bun's built-in test runner, complementing general testing-workflow skill.
## CRITICAL: Bun Test Execution
**NEVER use jest, vitest, or other test runners in Bun projects:**
```bash
# ✅ CORRECT - Bun test execution
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
# ❌ jest
# ❌ jest --watch
# ❌ npm run test (if mapped to jest)
# ❌ WRONG - Never use vitest in Bun projects
# ❌ vitest
# ❌ vitest run
```
**Always use `bun test` directly** (never use jest/vitest in Bun projects).
---
## 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
```
### 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
---
## 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);
});
});
```
### 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
});
});
});
```
---
## Bun Test API
### Describe and 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();
});
});
```
### 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/);
```
### 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", () => {});
});
});
```
### Test.todo
```typescript
import { it } from "bun:test";
it.todo("feature not yet implemented");
it.todo("edge case to handle");
```
---
## Setup and Teardown
### beforeEach and 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");
});
});
```
### beforeAll and 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();
});
});
```
### 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
});
});
});
```
---
## Mocking with Bun
### Using 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"]);
```
### Mock Objects and Modules
```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);
});
});
```
### Module Mocking
```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");
});
});
```
### Spy on Function Calls
```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);
});
});
```
### Mock Return Values
عرض على GitHub