Transform hub-guide from a single-skill Hub monorepo guide into a comprehensive programming best-practice plugin covering all situations. Skills (26): - Core: engineering-principles, clean-code, clean-architecture, design-patterns, testing, error-handling, security, api-design, git-workflow, documentation, logging-observability, performance - Languages: typescript, python, rust, go - Frameworks: react-frontend, elysiajs, hono-backend, drizzle-database, nextjs - Infrastructure: docker, ci-cd, monitoring - Monorepo: monorepo, hub-guide (existing) Hooks: - SessionStart: auto-detect project type and activate relevant skills - PreToolUse (Write|Edit): inject language-specific rules per file type Reference files for deep dives: - clean-architecture/references/solid.md (SOLID + component principles) - design-patterns/references/catalog.md (full GoF catalog with examples) - testing/references/mocks.md (test double taxonomy) Restructure plugin to modern skills/ directory format.
133 lines
5.1 KiB
Markdown
133 lines
5.1 KiB
Markdown
---
|
||
name: testing
|
||
description: Best practices for software testing — TDD, test pyramid, F.I.R.S.T. principles, mocking strategies, and test organization. Use when writing tests, designing test strategy, refactoring under test, or whenever the user mentions "unit test," "integration test," "TDD," "test coverage," "mock," "stub," "e2e test," "Jest," "Vitest," "pytest," "cargo test," or "testing."
|
||
---
|
||
|
||
# Testing Best Practices
|
||
|
||
## The Test Pyramid
|
||
|
||
```
|
||
╱╲
|
||
╱ E2E ╲ Few — critical user journeys
|
||
╱────────╲
|
||
╱ Integration ╲ Some — API, DB, external service boundaries
|
||
╱────────────────╲
|
||
╱ Unit Tests ╲ Many — domain logic, utilities, pure functions
|
||
╱────────────────────╲
|
||
```
|
||
|
||
- **Unit tests** — fast, isolated, test one behavior. 70%+ of tests.
|
||
- **Integration tests** — test boundaries (DB queries, API contracts, file IO).
|
||
- **E2E tests** — critical paths only. Slow and brittle — minimize.
|
||
|
||
## Three Laws of TDD
|
||
|
||
1. Don't write production code until you have a failing test.
|
||
2. Don't write more of a test than is sufficient to fail.
|
||
3. Don't write more production code than is sufficient to pass.
|
||
|
||
The cycle: Red (failing test) → Green (passing) → Refactor.
|
||
|
||
## F.I.R.S.T. Principles
|
||
|
||
- **Fast** — tests run quickly. If slow, they won't be run.
|
||
- **Independent** — no test depends on another. Any order, any subset.
|
||
- **Repeatable** — same result every time, in any environment.
|
||
- **Self-validating** — pass/fail is binary. No manual inspection.
|
||
- **Timely** — written *before* (or at the same time as) production code.
|
||
|
||
## Test Structure
|
||
|
||
### Arrange-Act-Assert (AAA)
|
||
```typescript
|
||
// Arrange
|
||
const user = new User('test@example.com');
|
||
const service = new AuthService(mockRepo);
|
||
|
||
// Act
|
||
const result = await service.login(user);
|
||
|
||
// Assert
|
||
expect(result.success).toBe(true);
|
||
```
|
||
|
||
### Naming
|
||
```typescript
|
||
describe('CreateOrderUseCase', () => {
|
||
it('throws when inventory is insufficient', async () => { ... });
|
||
it('creates order with correct total', async () => { ... });
|
||
it('deducts inventory on successful order', async () => { ... });
|
||
});
|
||
```
|
||
|
||
### One assertion per test? No — one *concept* per test.
|
||
Group related assertions for the same behavior:
|
||
```typescript
|
||
it('returns complete user profile', () => {
|
||
const profile = service.getProfile(userId);
|
||
expect(profile.name).toBe('Alice');
|
||
expect(profile.email).toBe('alice@example.com');
|
||
expect(profile.role).toBe('admin');
|
||
});
|
||
```
|
||
|
||
## What to Test
|
||
|
||
| Test | What | Example |
|
||
|------|------|---------|
|
||
| Domain logic | Business rules, calculations, validations | `PriceCalculator.calculateTotal()` |
|
||
| Edge cases | Empty state, null, max values, error paths | `Order.create({ items: [] })` |
|
||
| Public contracts | API endpoints, method signatures | `POST /orders returns 201` |
|
||
| Error handling | Expected failures, retry, fallback | `Repository.save() when DB down` |
|
||
|
||
## What NOT to Test
|
||
|
||
- ❌ Framework internals (React, Express, Drizzle — they have their own tests)
|
||
- ❌ Implementation details (private methods — test through public API)
|
||
- ❌ Simple one-liners with no logic (`getters`, `toString`)
|
||
- ❌ Configuration constants
|
||
|
||
## Mocking Strategies
|
||
|
||
- **Mock external boundaries only** — database, network, filesystem, clock.
|
||
- **Don't mock domain objects** — use real entities/value objects.
|
||
- **Mock roles, not objects** — mock the interface/port, not the concrete class.
|
||
- **Prefer fakes over mocks** for test doubles that have real behavior (e.g., in-memory DB).
|
||
- **Over-mocking is a smell** — tests that break on every refactor are testing implementation, not behavior.
|
||
|
||
### Mock Levels
|
||
```typescript
|
||
// ❌ Over-mocked: tests break on refactor, test internal wiring
|
||
const mockRepo = { save: vi.fn() };
|
||
const useCase = new CreateOrder(mockRepo);
|
||
mockRepo.save.mockResolvedValueOnce({ id: '1' });
|
||
|
||
// ✅ Better: test behavior through real fakes
|
||
class InMemoryOrderRepo implements OrderRepository {
|
||
private orders = new Map<string, Order>();
|
||
async save(o: Order) { this.orders.set(o.id, o); return o; }
|
||
async findById(id: string) { return this.orders.get(id) ?? null; }
|
||
}
|
||
```
|
||
|
||
## Test Coverage Guidelines
|
||
|
||
- **80-90% line coverage** is healthy for production code
|
||
- **100% is a red flag** — likely testing trivia and implementation details
|
||
- **Focus coverage on domain logic** (business rules) over infrastructure wrappers
|
||
- **Coverage is a lagging indicator** — good tests aren't about coverage, they're about confidence
|
||
|
||
## Deeper Reference
|
||
|
||
When the task calls for it, load:
|
||
|
||
- **[references/mocks.md](references/mocks.md)** — Complete test double taxonomy (Dummy, Fake, Stub, Mock, Spy). Code examples, when to use each, over-mocking traps, and anti-patterns.
|
||
|
||
## Language-Specific Directives
|
||
|
||
- **TypeScript:** Use Vitest over Jest (faster, ESM-native). Use `vi.fn()` sparingly.
|
||
- **Python:** Use pytest (not unittest). Fixtures over setup/teardown.
|
||
- **Rust:** Unit tests inline in module. Integration tests in `tests/` directory.
|
||
- **Go:** Tests in `_test.go` files. Table-driven tests for multiple cases.
|