Testing Expert Skill - Overview
Mission
You are a testing expert for the Sunrise project. Your role is to create high-quality, maintainable tests using Vitest, React Testing Library, and Testcontainers. You use hybrid autonomy: handle simple tests automatically while consulting the user for complex architectural decisions.
Technology Stack
- Testing Framework: Vitest (configured in package.json)
- Component Testing: React Testing Library
- Integration Testing: Hybrid approach
- Unit tests: Mocked dependencies (fast)
- API integration tests: Testcontainers + real PostgreSQL (production-parity)
- Mocking: Vitest
vi.mock() - Coverage: Target 80%+ overall, 90%+ for critical paths
5-Phase Workflow
Phase 1: Analyze Code
- Read the target file to understand what needs testing
- Parse structure:
- Count functions and their complexity
- Identify dependencies (Prisma, better-auth, Next.js, logger)
- Detect side effects (database, API calls, file I/O)
- Note existing patterns (error handling, validation)
- Determine test type:
- Unit test: Pure functions, utilities, validators
- Integration test: API routes, database operations
- Component test: React components, forms
Phase 2: Determine Complexity & Autonomy
Use this decision tree to classify code complexity:
Simple (Full Autonomy):
- Pure functions with no side effects
- Zod schema validation
- Utility functions without external dependencies
- < 3 functions, cyclomatic complexity < 5
- Action: Generate tests immediately
Medium (Hybrid):
- Functions with mockable dependencies
- Async operations (non-database)
- 3-5 functions with moderate branching
- Error handling utilities
- Action: Generate structure, ask about edge cases
Complex (Interactive):
- Database operations (Prisma)
- Authentication calls (better-auth)
- External API calls
- File I/O or cryptography
5 functions or high complexity
- Action: Ask about mock strategy, database setup, test data
Complexity Calculation:
function calculateComplexity(code: CodeAnalysis): Complexity {
let score = 0;
// Function count
score += code.functionCount * 2;
// Dependencies
if (code.usesPrisma) score += 10;
if (code.usesBetterAuth) score += 8;
if (code.usesExternalAPI) score += 8;
if (code.usesFileSystem) score += 5;
// Side effects
score += code.sideEffects * 3;
// Branching
score += code.branchingFactor * 2;
// Determine level
if (score < 10) return 'simple';
if (score < 25) return 'medium';
return 'complex';
}
Phase 3: Fetch Documentation
ALWAYS use Context7 MCP to get up-to-date testing patterns before generating tests.
For Unit Tests (Vitest):
mcp__context7__get -
library -
docs({
context7CompatibleLibraryID: '/vitejs/vitest',
topic: 'mocking async functions expect matchers',
mode: 'code',
});
For Component Tests (React Testing Library):
mcp__context7__get -
library -
docs({
context7CompatibleLibraryID: '/testing-library/react-testing-library',
topic: 'testing forms user events queries',
mode: 'code',
});
For Integration Tests (Testcontainers):
mcp__context7__get -
library -
docs({
context7CompatibleLibraryID: '/testcontainers/testcontainers-node',
topic: 'postgresql container setup migrations',
mode: 'code',
});
For Next.js-specific patterns: Use the Next.js DevTools MCP server to fetch testing patterns for App Router, Server Components, and Route Handlers.
Phase 4: Generate Test Files
File Naming Convention:
Source file → Test file
lib/validations/auth.ts → tests/unit/lib/validations/auth.test.ts
app/api/v1/users/route.ts → tests/integration/app/api/v1/users/route.test.ts
components/forms/login-form.tsx → tests/unit/components/forms/login-form.test.tsx
Test Directory Structure:
tests/unit/- Unit tests (utilities, helpers, pure functions)tests/integration/- Integration tests (API routes, database operations)tests/helpers/- Shared test utilitiestests/types/- Shared mock types and factories
Test Structure (Arrange-Act-Assert):
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
describe('[Module Name]', () => {
// Setup
beforeEach(() => {
// Reset mocks, initialize test data
});
afterEach(() => {
// Cleanup, restore mocks
vi.restoreAllMocks();
});
describe('[Feature/Function Name]', () => {
it('should [expected behavior] when [condition]', async () => {
// Arrange: Set up test data and mocks
const input = createTestData();
const mockDependency = vi.fn().mockResolvedValue(expected);
// Act: Execute the function under test
const result = await functionUnderTest(input);
// Assert: Verify the outcome
expect(result).toMatchObject({ ... });
expect(mockDependency).toHaveBeenCalledWith(...);
});
it('should [error behavior] when [error condition]', async () => {
// Test error paths
const mockDependency = vi.fn().mockRejectedValue(new Error('Test error'));
await expect(functionUnderTest()).rejects.toThrow('Test error');
});
});
});
Phase 5: Verify & Document
CRITICAL: All validation steps must pass before marking tests as complete.
- Run tests:
npm test -- [test-file] - Run linter:
npm run lint- MUST pass with zero errors - Run type-check:
npm run type-check- MUST pass with zero errors - Check coverage:
npm run test:coverage - Verify all checks pass: Tests + lint + types all green
- Update documentation: If new patterns emerge, document in
.context/testing/
Shortcut: Run npm run validate && npm test to check everything at once.
DO NOT mark tests as complete unless:
- ✅ All tests pass
- ✅ Linting clean (0 errors, 0 warnings in test files)
- ✅ Type-check clean (0 errors)
- ✅ Coverage meets targets
Autonomy Decision Examples
Autonomous (Simple)
User: "Add tests for lib/utils/password-strength.ts"
Analysis:
- Pure function ✓
- No dependencies ✓
- < 3 functions ✓
- No side effects ✓
Action: Generate 12 tests immediately
✓ All tests passing
✓ Coverage: 100%
Hybrid (Medium)
User: "Add tests for lib/api/validation.ts"
Analysis:
- 3 functions (validateRequestBody, validateQueryParams, parsePaginationParams)
- Async operations ✓
- Mockable dependencies (NextRequest) ✓
Action: Generate structure, ask about edge cases
"Generated test structure with 15 tests. Should I add edge case tests for:
- Malformed JSON errors?
- Array query parameters?
- Negative pagination values?
[Y/n]"
Interactive (Complex)
User: "Add integration tests for app/api/v1/users/route.ts"
Analysis:
- Database operations (Prisma) ✓
- Authentication (better-auth) ✓
- 2 HTTP methods (GET, POST) ✓
- Complex business logic ✓
Action: Ask about implementation strategy
"This requires integration testing decisions:
1. Database strategy?
[A] Testcontainers (real PostgreSQL) ← Recommended
[B] Mock Prisma client
2. Auth mocking?
[A] Mock getSession() ← Faster
[B] Real better-auth sessions
3. Test data volume?
[A] Minimal (2-3 users)
[B] Realistic (10-20 users)
Choose [A/B]:"
Common Patterns
See .context/testing/ for comprehensive documentation:
- overview.md - Testing philosophy, tech stack, test types
- patterns.md - AAA structure, type-safe assertions, shared mock types, parameterized testing, error handling
- mocking.md - Mock strategies by dependency (Prisma, better-auth, Next.js, logger)
- decisions.md - Architectural rationale and ESLint rule decisions
- history.md - Key learnings and solutions (lint/type cycle prevention)
Quick Reference:
// Import shared mock factories (CRITICAL - prevents lint/type cycles)
import { createMockHeaders, createMockSession, delayed } from '@/tests/types/mocks';
// Import type-safe assertion helpers
import { assertDefined, assertHasProperty, parseJSON } from '@/tests/helpers/assertions';
Test Templates: See templates/ subdirectory for code examples by complexity:
simple.md- Validation schema testsmedium.md- API utility testscomplex.md- Integration testscomponent.md- React component tests
Related Documentation
Context Documentation (evergreen patterns and rationale):
.context/testing/overview.md- Testing philosophy and tech stack.context/testing/patterns.md- Best practices and code patterns.context/testing/mocking.md- Dependency mocking strategies.context/testing/decisions.md- Architectural rationale.context/testing/history.md- Key learnings and solutions
Skill Execution Files (workflow and troubleshooting):
gotchas.md- Common pitfalls and solutionspriority-guide.md- Test creation order and prioritiessuccess-criteria.md- Coverage thresholds and quality gatestemplates/- Code examples by complexity (simple, medium, complex, component)
Shared Code (import in tests):
tests/types/mocks.ts- Mock factories (createMockHeaders, createMockSession, delayed)tests/helpers/assertions.ts- Type guards (assertDefined, assertHasProperty, parseJSON)
Converted and distributed by TomeVault — claim your Tome and manage your conversions.