ACTIVATE when building new features, endpoints, or user stories using TDD with Vitest. ACTIVATE for '/feature-dev', 'TDD', 'test first', 'red-green-refactor', 'iterations' in a TypeScript/NestJS context. Covers: cross-layer TDD iterations (Controller/Domain/Repository), working app at each GREEN, bug-fix-first-test workflow, mocks-hiding-bugs pitfall. DO NOT use for: test writing conventions (see vitest-test-conventions), PHP TDD (see php-tdd-workflow).
From vitestnpx claudepluginhub fabiensalles/claude-marketplace --plugin vitestThis skill uses the workspace's default tool permissions.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
Guides idea refinement into designs: explores context, asks questions one-by-one, proposes approaches, presents sections for approval, writes/review specs before coding.
This skill documents the Test-Driven Development workflow for Quittance.me.
See also:
vitest-test-conventionsfor test writing conventions (DAMP, test doubles, factories).
Test First, Always. Every feature follows TDD with small iterations:
Analyze the complete feature before coding:
Output: Complete understanding, but implementation is iterative.
Key principle: Small iterations across all layers, not layer-by-layer completion.
Iteration 1: Controller stub + first Domain behavior
↓
Iteration 2: Domain logic + Repository interface
↓
Iteration 3: Back to Controller → complete happy path
↓
Iteration 4: Edge case handling
↓
...continue until feature complete
Each iteration can touch Controller, Domain, and Repository as needed.
Iteration 1: Controller route exists
Test: E2E → authenticated user gets 200
Implement: Empty controller with route
Iteration 2: Eligibility check
Test: E2E → tenant without lease gets 403
Implement: Guard or check in controller
Iteration 3: Domain logic
Test: Unit → receipt calculates correct amount
Implement: Receipt domain model
Iteration 4: Back to controller — wire it up
Test: E2E → verify full flow works
Implement: Connect domain to controller
Iteration 5: Edge case
Test: Unit → partial month prorates correctly
Implement: Update calculation logic
┌──────────────────────────────────────────────────┐
│ Happy Path │
│ Controller → Domain → Repository → Controller │
├──────────────────────────────────────────────────┤
│ Edge Cases │
│ Add tests for each edge case, implement fixes │
└──────────────────────────────────────────────────┘
MANDATORY: At the end of each GREEN iteration, the application MUST be functional.
// ❌ FORBIDDEN: referencing a non-existent service
@Controller('receipts')
export class ReceiptController {
constructor(private readonly service: ReceiptService) {} // Not registered!
}
// → Test may pass with mocks, but app crashes at runtime!
// ✅ CORRECT: register the service or use a stub
@Module({
providers: [ReceiptService], // Registered
})
export class ReceiptModule {}
Tests pass AND the application works at each iteration.
| Check | Action |
|---|---|
| Service injected? | Register in module providers |
| Repository used? | Implement or create a stub |
| Module imported? | Add to parent module imports |
| Guard added? | Register in module |
MANDATORY: After each phase (RED or GREEN), run the specific test:
# Run specific test file
pnpm test src/receipt/receipt.service.spec.ts
# Run tests matching a pattern
pnpm vitest run --reporter=verbose receipt
# Run all tests
pnpm test
Never assume a test passes without running it.
CRITICAL: When something is broken, follow this workflow before any code change:
# Search for test class related to the broken component
pnpm vitest run --reporter=verbose receipt
Ask yourself:
it('should calculate prorated amount for partial month', () => {
// Use REAL dependencies (not mocks) to expose the issue
const receipt = Receipt.create(lease, partialPeriod);
// This assertion should FAIL with the current implementation
expect(receipt.amount).toBe(387_10); // cents
});
pnpm vitest run src/receipt/receipt.spec.ts
If the test passes, your test doesn't reproduce the issue. Investigate further.
Only now write the fix. The failing test guides the solution.
// ❌ This test passes but hides a calculation bug
const repo = { findLease: vi.fn().mockResolvedValue(mockLease) };
// ✅ Use real domain objects to catch real problems
const lease = Lease.create(realLeaseData);
const receipt = Receipt.create(lease, period);
| Code Created | Tests Required |
|---|---|
| Domain model | Unit test for business logic |
| Controller | E2E test for endpoint |
| Repository | Integration test with database |
| Use case | Unit test with mocked ports |
| Guard | Unit test for access rules |
# All tests
pnpm test
# Specific test file
pnpm vitest run src/receipt/receipt.service.spec.ts
# Watch mode
pnpm vitest src/receipt/
# Filter by test name
pnpm vitest run -t "prorated amount"