10 KiB
Testing Patterns
Analysis Date: 2026-02-24
Test Framework
Runner:
- Vitest 2.1.0
- Config: No dedicated
vitest.config.tsfound (uses defaults) - Node.js test environment
Assertion Library:
- Vitest native assertions via
expect() - Examples:
expect(value).toBe(),expect(value).toBeDefined(),expect(array).toContain()
Run Commands:
npm test # Run all tests once
npm run test:watch # Watch mode for continuous testing
npm run test:coverage # Generate coverage report
Coverage Tool:
@vitest/coverage-v82.1.0- Tracks line, branch, function, and statement coverage
- V8 backend for accurate coverage metrics
Test File Organization
Location:
- Co-located in
backend/src/__tests__/directory - Subdirectories for logical grouping:
backend/src/__tests__/utils/- Utility function testsbackend/src/__tests__/mocks/- Mock implementationsbackend/src/__tests__/acceptance/- Acceptance/integration tests
Naming:
- Pattern:
[feature].test.tsor[feature].spec.ts - Examples:
backend/src/__tests__/financial-summary.test.tsbackend/src/__tests__/acceptance/handiFoods.acceptance.test.ts
Structure:
backend/src/__tests__/
├── utils/
│ └── test-helpers.ts # Test utility functions
├── mocks/
│ └── logger.mock.ts # Mock implementations
└── acceptance/
└── handiFoods.acceptance.test.ts # Acceptance tests
Test Structure
Suite Organization:
import { describe, test, expect, beforeAll } from 'vitest';
describe('Feature Category', () => {
describe('Nested Behavior Group', () => {
test('should do specific thing', () => {
expect(result).toBe(expected);
});
test('should handle edge case', () => {
expect(edge).toBeDefined();
});
});
});
From financial-summary.test.ts:
describe('Financial Summary Fixes', () => {
describe('Period Ordering', () => {
test('Summary table should display periods in chronological order (FY3 → FY2 → FY1 → LTM)', () => {
const periods = ['fy3', 'fy2', 'fy1', 'ltm'];
const expectedOrder = ['FY3', 'FY2', 'FY1', 'LTM'];
expect(periods[0]).toBe('fy3');
expect(periods[3]).toBe('ltm');
});
});
});
Patterns:
-
Setup Pattern:
- Use
beforeAll()for shared test data initialization - Example from
handiFoods.acceptance.test.ts:beforeAll(() => { const normalize = (text: string) => text.replace(/\s+/g, ' ').toLowerCase(); const cimRaw = fs.readFileSync(cimTextPath, 'utf-8'); const outputRaw = fs.readFileSync(outputTextPath, 'utf-8'); cimNormalized = normalize(cimRaw); outputNormalized = normalize(outputRaw); });
- Use
-
Teardown Pattern:
- Not explicitly shown in current tests
- Use
afterAll()for resource cleanup if needed
-
Assertion Pattern:
- Descriptive test names that read as sentences:
'should display periods in chronological order' - Multiple assertions per test acceptable for related checks
- Use
expect().toContain()for array/string membership - Use
expect().toBeDefined()for existence checks - Use
expect().toBeGreaterThan()for numeric comparisons
- Descriptive test names that read as sentences:
Mocking
Framework: Vitest vi mock utilities
Patterns:
-
Mock Logger:
import { vi } from 'vitest'; export const mockLogger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), }; export const mockStructuredLogger = { uploadStart: vi.fn(), uploadSuccess: vi.fn(), uploadError: vi.fn(), processingStart: vi.fn(), processingSuccess: vi.fn(), processingError: vi.fn(), storageOperation: vi.fn(), jobQueueOperation: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), }; -
Mock Service Pattern:
- Create mock implementations in
backend/src/__tests__/mocks/ - Export as named exports:
export const mockLogger,export const mockStructuredLogger - Use
vi.fn()for all callable methods to track calls and arguments
- Create mock implementations in
-
What to Mock:
- External services: Firebase Auth, Supabase, Google Cloud APIs
- Logger: always mock to prevent log spam during tests
- File system operations (in unit tests; use real files in acceptance tests)
- LLM API calls: mock responses to avoid quota usage
-
What NOT to Mock:
- Core utility functions: use real implementations
- Type definitions: no need to mock types
- Pure functions: test directly without mocks
- Business logic calculations: test with real data
Fixtures and Factories
Test Data:
-
Helper Factory Pattern: From
backend/src/__tests__/utils/test-helpers.ts:export function createMockCorrelationId(): string { return `test-correlation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } export function createMockUserId(): string { return `test-user-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } export function createMockDocumentId(): string { return `test-doc-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } export function createMockJobId(): string { return `test-job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } export function wait(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } -
Acceptance Test Fixtures:
- Located in
backend/test-fixtures/directory - Example:
backend/test-fixtures/handiFoods/contains:handi-foods-cim.txt- Reference CIM contenthandi-foods-output.txt- Expected processor output
- Loaded via
fs.readFileSync()inbeforeAll()hooks
- Located in
Location:
- Test helpers:
backend/src/__tests__/utils/test-helpers.ts - Acceptance fixtures:
backend/test-fixtures/(outside src) - Mocks:
backend/src/__tests__/mocks/
Coverage
Requirements:
- No automated coverage enforcement detected (no threshold in config)
- Manual review recommended for critical paths
View Coverage:
npm run test:coverage
Test Types
Unit Tests:
- Scope: Individual functions, services, utilities
- Approach: Test in isolation with mocks for dependencies
- Examples:
- Financial parser tests: parse tables with various formats
- Period ordering tests: verify chronological order logic
- Validate UUID format tests: regex pattern matching
- Location:
backend/src/__tests__/[feature].test.ts
Integration Tests:
- Scope: Multiple components working together
- Approach: May use real Supabase/Firebase or mocks depending on test level
- Not heavily used: minimal integration test infrastructure
- Pattern: Could use real database in test environment with cleanup
Acceptance Tests:
- Scope: End-to-end feature validation with real artifacts
- Approach: Load reference files, process through entire pipeline, verify output
- Example:
handiFoods.acceptance.test.ts- Loads CIM text file
- Loads processor output file
- Validates all reference facts exist in both
- Validates key fields resolved instead of fallback messages
- Location:
backend/src/__tests__/acceptance/
E2E Tests:
- Not implemented in current setup
- Would require browser automation (no Playwright/Cypress config found)
- Frontend testing: not currently automated
Common Patterns
Async Testing:
test('should process document asynchronously', async () => {
const result = await processDocument(documentId, userId, text);
expect(result.success).toBe(true);
});
Error Testing:
test('should validate UUID format', () => {
const id = 'invalid-id';
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
expect(uuidRegex.test(id)).toBe(false);
});
Array/Collection Testing:
test('should extract all financial periods', () => {
const result = parseFinancialsFromText(tableText);
expect(result.data.fy3.revenue).toBeDefined();
expect(result.data.fy2.revenue).toBeDefined();
expect(result.data.fy1.revenue).toBeDefined();
expect(result.data.ltm.revenue).toBeDefined();
});
Text/Content Testing (Acceptance):
test('verifies each reference fact exists in CIM and generated output', () => {
for (const fact of referenceFacts) {
for (const token of fact.tokens) {
expect(cimNormalized).toContain(token);
expect(outputNormalized).toContain(token);
}
}
});
Normalization for Content Testing:
// Normalize whitespace and case for robust text matching
const normalize = (text: string) => text.replace(/\s+/g, ' ').toLowerCase();
const normalizedCIM = normalize(cimRaw);
expect(normalizedCIM).toContain('reference-phrase');
Test Coverage Priorities
Critical Paths (Test First):
- Document upload and file storage operations
- Firebase authentication and token validation
- LLM service API interactions with retry logic
- Error handling and correlation ID tracking
- Financial data extraction and parsing
- PDF generation pipeline
Important Paths (Test Early):
- Vector embeddings and database operations
- Job queue processing and timeout handling
- Google Document AI text extraction
- Supabase Row Level Security policies
Nice-to-Have (Test Later):
- UI component rendering (would require React Testing Library)
- CSS/styling validation
- Frontend form submission flows
- Analytics tracking
Current Testing Gaps
Untested Areas:
- Backend services: Most services lack unit tests (llmService, fileStorageService, etc.)
- Database models: No model tests for Supabase operations
- Controllers/Endpoints: No API endpoint tests
- Frontend components: No React component tests
- Integration flows: Document upload through processing to PDF generation
Missing Patterns:
- No database integration test setup (fixtures, transactions)
- No API request/response validation tests
- No performance/load tests
- No security tests (auth bypass, XSS, injection)
Deprecated Test Patterns (DO NOT USE)
- ❌ Jest test suite - Use Vitest instead
- ❌ Direct PostgreSQL connection tests - Use Supabase in test mode
- ❌ Legacy test files referencing removed services - Updated implementations used only
Testing analysis: 2026-02-24