tests.md
Good and Bad Tests
Section titled “Good and Bad Tests”Good Tests
Section titled “Good Tests”Integration-style: Test through real interfaces, not mocks of internal parts.
// GOOD: Tests observable behaviortest("user can checkout with valid cart", async () => { const cart = createCart(); cart.add(product); const result = await checkout(cart, paymentMethod); expect(result.status).toBe("confirmed");});Characteristics:
- Tests behavior users/callers care about
- Uses public API only
- Survives internal refactors
- Describes WHAT, not HOW
- One logical assertion per test
Bad Tests
Section titled “Bad Tests”Implementation-detail tests: Coupled to internal structure.
// BAD: Tests implementation detailstest("checkout calls paymentService.process", async () => { const mockPayment = jest.mock(paymentService); await checkout(cart, payment); expect(mockPayment.process).toHaveBeenCalledWith(cart.total);});Red flags:
- Mocking internal collaborators
- Testing private methods
- Asserting on call counts/order
- Test breaks when refactoring without behavior change
- Test name describes HOW not WHAT
- Verifying through external means instead of interface
// BAD: Bypasses interface to verifytest("createUser saves to database", async () => { await createUser({ name: "Alice" }); const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]); expect(row).toBeDefined();});
// GOOD: Verifies through interfacetest("createUser makes user retrievable", async () => { const user = await createUser({ name: "Alice" }); const retrieved = await getUser(user.id); expect(retrieved.name).toBe("Alice");});Tautological tests: Expected value restates the implementation, so the test passes by construction.
// BAD: Expected value is recomputed the way the code computes ittest("calculateTotal sums line items", () => { const items = [{ price: 10 }, { price: 5 }]; const expected = items.reduce((sum, i) => sum + i.price, 0); expect(calculateTotal(items)).toBe(expected);});
// GOOD: Expected value is an independent, known literaltest("calculateTotal sums line items", () => { expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);});Original Markdown source
# Good and Bad Tests
## Good Tests
**Integration-style**: Test through real interfaces, not mocks of internal parts.
```typescript// GOOD: Tests observable behaviortest("user can checkout with valid cart", async () => { const cart = createCart(); cart.add(product); const result = await checkout(cart, paymentMethod); expect(result.status).toBe("confirmed");});```
Characteristics:
- Tests behavior users/callers care about- Uses public API only- Survives internal refactors- Describes WHAT, not HOW- One logical assertion per test
## Bad Tests
**Implementation-detail tests**: Coupled to internal structure.
```typescript// BAD: Tests implementation detailstest("checkout calls paymentService.process", async () => { const mockPayment = jest.mock(paymentService); await checkout(cart, payment); expect(mockPayment.process).toHaveBeenCalledWith(cart.total);});```
Red flags:
- Mocking internal collaborators- Testing private methods- Asserting on call counts/order- Test breaks when refactoring without behavior change- Test name describes HOW not WHAT- Verifying through external means instead of interface
```typescript// BAD: Bypasses interface to verifytest("createUser saves to database", async () => { await createUser({ name: "Alice" }); const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]); expect(row).toBeDefined();});
// GOOD: Verifies through interfacetest("createUser makes user retrievable", async () => { const user = await createUser({ name: "Alice" }); const retrieved = await getUser(user.id); expect(retrieved.name).toBe("Alice");});```
**Tautological tests**: Expected value restates the implementation, so the test passes by construction.
```typescript// BAD: Expected value is recomputed the way the code computes ittest("calculateTotal sums line items", () => { const items = [{ price: 10 }, { price: 5 }]; const expected = items.reduce((sum, i) => sum + i.price, 0); expect(calculateTotal(items)).toBe(expected);});
// GOOD: Expected value is an independent, known literaltest("calculateTotal sums line items", () => { expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);});```