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.
5.1 KiB
5.1 KiB
name, description
| name | description |
|---|---|
| testing | 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
- Don't write production code until you have a failing test.
- Don't write more of a test than is sufficient to fail.
- 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)
// 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
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:
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
// ❌ 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 — 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.gofiles. Table-driven tests for multiple cases.