Overview#
The Oshun monorepo uses a layered testing approach with Vitest for unit and integration tests, Playwright for E2E tests, and k6 for performance tests.
Test Framework: Vitest#
Configuration Hierarchy#
testing/
├── vitest.config.base.ts # Shared base configuration
├── vitest.workspace.ts # Multi-project workspace definition
├── setup/
│ ├── vitest.setup.ts # Global test setup
│ └── vitest.jest-compat.ts # Jest compatibility layer
└── ...
Each project extends the base configuration with minimal overrides:
// libs/{domain}/{lib}/vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config';
import baseConfig from '../../../testing/vitest.config.base';
export default mergeConfig(
baseConfig,
defineConfig({
test: {
// Project-specific overrides only
},
})
);
Base Configuration#
Global settings:
- Environment:
node - Globals: enabled (Jest-compatible
describe,it,expect) - ESBuild target:
node20
Test file patterns:
src/**/*.{test,spec}.{ts,tsx}
__tests__/**/*.{test,spec}.{ts,tsx}
tests/**/*.{test,spec}.{ts,tsx}
Exclusions: node_modules, dist, build, .nx, E2E tests, integration
tests, performance tests
Timeouts:
- Test timeout: 30 seconds
- Hook timeout: 30 seconds
- Teardown timeout: 10 seconds
Thread pool:
- CI: Maximum 4 isolated threads
- Development: Unlimited threads
Retry policy (CI only):
- Retry count: 2 attempts
- Bail after: 5 failures
Coverage#
Provider: v8
Thresholds (enforced in CI):
| Metric | Minimum |
|---|---|
| Branches | 80% |
| Functions | 80% |
| Lines | 80% |
| Statements | 80% |
Reporters: text, text-summary, json, html, lcov, cobertura
Coverage reports are generated in each project's coverage/ directory.
Path Aliases#
The base configuration maps @oshun/* and domain-specific imports to source
directories so tests resolve correctly without building first:
// Shared libraries
'@oshun/types' → 'libs/shared/types/src'
'@oshun/errors' → 'libs/shared/errors/src'
'@oshun/database' → 'libs/shared/database/src'
// ... (all @oshun/* libraries)
// Domain contracts
'@oshun/contracts/{domain}' → 'libs/contracts/{domain}/src'
// Domain clients
'@oshun/clients/{domain}' → 'libs/shared/clients/{domain}/src'
Test Types#
Unit Tests#
Run with pnpm nx test <project>:
# Test a specific library
pnpm nx test @oshun/database
# Test with coverage
pnpm nx test @oshun/database --coverage
# Test all affected projects
pnpm nx affected --target=test
Unit tests mock external dependencies and focus on individual module behavior.
Integration Tests#
Configuration: testing/vitest.config.integration.ts
Integration tests verify interactions between modules and with real databases. They typically require Docker services to be running:
# Start infrastructure first
docker compose -f docker/docker-compose.dev.yml up -d
# Run integration tests
pnpm nx test:integration <project>
E2E Tests#
Framework: Playwright
Configuration: testing/e2e/
E2E tests validate complete user journeys through web applications:
# Run E2E tests
pnpm nx e2e <project>
# Run with headed browser
pnpm nx e2e <project> --headed
The CI pipeline shards E2E tests across 3 workers for faster execution.
Performance Tests#
Framework: k6
Configuration: testing/performance/
# Run performance benchmarks
pnpm nx perf <project>
The benchmarks.yml workflow runs k6 tests weekly with smoke, load, and stress
modes.
Testing Specialized Areas#
Mobile Tests#
Configuration: testing/mobile/
Mobile test utilities for React Native apps (Tara, Veritas, Psyche).
Security Tests#
Configuration: testing/security/
Security testing utilities including vulnerability scanning and input validation.
Chaos Tests#
Configuration: testing/chaos/
Chaos engineering tests to verify resilience and graceful degradation.
Boundary Tests#
Configuration: testing/boundary/
Boundary testing for edge cases and limit validation.
Running Tests#
Common Commands#
# Test a specific project
pnpm nx test @oshun/database
# Test all projects
pnpm test
# Test affected projects only
pnpm nx affected --target=test
# Test with watch mode
pnpm nx test @oshun/database --watch
# Test with coverage
pnpm nx test @oshun/database --coverage
# Run a specific test file
pnpm nx test @oshun/database -- --testPathPattern="sql.spec"
CI Output#
In CI, test results are output in multiple formats:
test-results/vitest-results.json- Machine-readable resultstest-results/junit.xml- JUnit XML for CI integrationtest-results/html/- HTML report for visual inspection
Writing Tests#
Conventions#
- File naming:
*.spec.tsor*.test.tsalongside the source file - Describe blocks: Match the module or function being tested
- Test names: Start with "should" and describe expected behavior
- Arrange-Act-Assert: Structure each test clearly
Example#
import { describe, it, expect, vi } from 'vitest';
import { myFunction } from './my-module';
describe('myFunction', () => {
it('should return the expected result', () => {
const result = myFunction('input');
expect(result).toBe('expected');
});
it('should throw on invalid input', () => {
expect(() => myFunction('')).toThrow();
});
});
Common Patterns#
- Use
vi.mock()for module mocking - Use
vi.fn()for function spies - Use
toEqual()for deep equality (nottoBe()for objects) - Use
toContainEqual()for array membership with deep equality - Use
toBeCloseTo()for floating point comparisons - Prefix unused parameters with
_to satisfy@typescript-eslint/no-unused-vars
Further Reading#
- CI/CD Pipelines - How tests run in CI
- Getting Started - Development setup
- Architecture - System architecture