- Organized docs into numbered shelf/book/chapter/page structure - Created comprehensive README.md with project overview - Added Getting Started book (quick start, installation, configuration) - Added Development book (workflow, testing, type sharing) - Added Architecture book (design principles, PDA-friendly patterns) - Added API Reference book (conventions, authentication) - Moved TYPE-SHARING.md to proper location - Updated all cross-references in main README - Created docs/README.md as master index - Removed old QA automation reports - Removed deprecated SETUP.md (content split into new structure) Documentation structure follows Bookstack best practices: - Numbered books: 1-getting-started, 2-development, 3-architecture, 4-api - Numbered chapters and pages for ordering - Clear hierarchy and navigation - Cross-referenced throughout Complete documentation available at: docs/README.md Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
337 lines
6.5 KiB
Markdown
337 lines
6.5 KiB
Markdown
# Testing Requirements
|
|
|
|
Test-driven development standards and practices for Mosaic Stack.
|
|
|
|
## Testing Philosophy
|
|
|
|
- **Test-Driven Development (TDD)** — Write tests before implementation
|
|
- **Minimum 85% coverage** for all new code
|
|
- **All tests must pass** before PR approval
|
|
- **No untested code** in production
|
|
|
|
## Test Types
|
|
|
|
### Unit Tests
|
|
|
|
Test individual functions and methods in isolation.
|
|
|
|
**Location:** `*.spec.ts` next to source file
|
|
|
|
**Example:**
|
|
```typescript
|
|
// apps/api/src/auth/auth.service.spec.ts
|
|
describe('AuthService', () => {
|
|
it('should create a session for valid user', async () => {
|
|
const result = await authService.createSession(mockUser);
|
|
expect(result.session.token).toBeDefined();
|
|
});
|
|
});
|
|
```
|
|
|
|
### Integration Tests
|
|
|
|
Test interactions between components.
|
|
|
|
**Location:** `*.integration.spec.ts` in module directory
|
|
|
|
**Example:**
|
|
```typescript
|
|
// apps/api/src/auth/auth.integration.spec.ts
|
|
describe('Auth Integration', () => {
|
|
it('should complete full login flow', async () => {
|
|
const login = await request(app.getHttpServer())
|
|
.post('/auth/sign-in')
|
|
.send({ email, password });
|
|
expect(login.status).toBe(200);
|
|
});
|
|
});
|
|
```
|
|
|
|
### E2E Tests
|
|
|
|
Test complete user flows across the entire stack.
|
|
|
|
**Location:** `apps/web/tests/e2e/` (when implemented)
|
|
|
|
**Framework:** Playwright
|
|
|
|
## Running Tests
|
|
|
|
```bash
|
|
# All tests
|
|
pnpm test
|
|
|
|
# Watch mode (re-run on changes)
|
|
pnpm test:watch
|
|
|
|
# Coverage report
|
|
pnpm test:coverage
|
|
|
|
# Specific file
|
|
pnpm test apps/api/src/auth/auth.service.spec.ts
|
|
|
|
# API tests only
|
|
pnpm test:api
|
|
|
|
# E2E tests (when implemented)
|
|
pnpm test:e2e
|
|
```
|
|
|
|
## Writing Tests
|
|
|
|
### Structure
|
|
|
|
```typescript
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
|
describe('ComponentName', () => {
|
|
beforeEach(() => {
|
|
// Setup
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Cleanup
|
|
});
|
|
|
|
describe('methodName', () => {
|
|
it('should handle normal case', () => {
|
|
// Arrange
|
|
const input = 'test';
|
|
|
|
// Act
|
|
const result = component.method(input);
|
|
|
|
// Assert
|
|
expect(result).toBe('expected');
|
|
});
|
|
|
|
it('should handle error case', () => {
|
|
expect(() => component.method(null)).toThrow();
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
### Mocking
|
|
|
|
```typescript
|
|
// Mock service dependency
|
|
const mockPrismaService = {
|
|
user: {
|
|
findUnique: vi.fn(),
|
|
create: vi.fn(),
|
|
},
|
|
};
|
|
|
|
// Mock module
|
|
vi.mock('./some-module', () => ({
|
|
someFunction: vi.fn(() => 'mocked'),
|
|
}));
|
|
```
|
|
|
|
### Testing Async Code
|
|
|
|
```typescript
|
|
it('should complete async operation', async () => {
|
|
const result = await asyncFunction();
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
// Or with resolves/rejects
|
|
it('should resolve with data', async () => {
|
|
await expect(asyncFunction()).resolves.toBe('data');
|
|
});
|
|
|
|
it('should reject with error', async () => {
|
|
await expect(failingFunction()).rejects.toThrow('Error');
|
|
});
|
|
```
|
|
|
|
## Coverage Requirements
|
|
|
|
### Minimum Coverage
|
|
|
|
- **Statements:** 85%
|
|
- **Branches:** 80%
|
|
- **Functions:** 85%
|
|
- **Lines:** 85%
|
|
|
|
### View Coverage Report
|
|
|
|
```bash
|
|
pnpm test:coverage
|
|
|
|
# Opens HTML report
|
|
open coverage/index.html
|
|
```
|
|
|
|
### Exemptions
|
|
|
|
Some code types may have lower coverage requirements:
|
|
- **DTOs/Interfaces:** No coverage required (type checking sufficient)
|
|
- **Constants:** No coverage required
|
|
- **Database migrations:** Manual verification acceptable
|
|
|
|
Always document exemptions in PR description.
|
|
|
|
## Best Practices
|
|
|
|
### 1. Test Behavior, Not Implementation
|
|
|
|
**❌ Bad:**
|
|
```typescript
|
|
it('should call getUserById', () => {
|
|
service.login(email, password);
|
|
expect(mockService.getUserById).toHaveBeenCalled();
|
|
});
|
|
```
|
|
|
|
**✅ Good:**
|
|
```typescript
|
|
it('should return session for valid credentials', async () => {
|
|
const result = await service.login(email, password);
|
|
expect(result.session.token).toBeDefined();
|
|
expect(result.user.email).toBe(email);
|
|
});
|
|
```
|
|
|
|
### 2. Use Descriptive Test Names
|
|
|
|
**❌ Bad:**
|
|
```typescript
|
|
it('works', () => { ... });
|
|
it('test 1', () => { ... });
|
|
```
|
|
|
|
**✅ Good:**
|
|
```typescript
|
|
it('should return 401 for invalid credentials', () => { ... });
|
|
it('should create session with 24h expiration', () => { ... });
|
|
```
|
|
|
|
### 3. Arrange-Act-Assert Pattern
|
|
|
|
```typescript
|
|
it('should calculate total correctly', () => {
|
|
// Arrange - Set up test data
|
|
const items = [{ price: 10 }, { price: 20 }];
|
|
|
|
// Act - Execute the code being tested
|
|
const total = calculateTotal(items);
|
|
|
|
// Assert - Verify the result
|
|
expect(total).toBe(30);
|
|
});
|
|
```
|
|
|
|
### 4. Test Edge Cases
|
|
|
|
```typescript
|
|
describe('validateEmail', () => {
|
|
it('should accept valid email', () => {
|
|
expect(validateEmail('user@example.com')).toBe(true);
|
|
});
|
|
|
|
it('should reject empty string', () => {
|
|
expect(validateEmail('')).toBe(false);
|
|
});
|
|
|
|
it('should reject null', () => {
|
|
expect(validateEmail(null)).toBe(false);
|
|
});
|
|
|
|
it('should reject invalid format', () => {
|
|
expect(validateEmail('notanemail')).toBe(false);
|
|
});
|
|
});
|
|
```
|
|
|
|
### 5. Keep Tests Independent
|
|
|
|
```typescript
|
|
// ❌ Bad - Tests depend on order
|
|
let userId;
|
|
it('should create user', () => {
|
|
userId = createUser();
|
|
});
|
|
it('should get user', () => {
|
|
getUser(userId); // Fails if previous test fails
|
|
});
|
|
|
|
// ✅ Good - Each test is independent
|
|
it('should create user', () => {
|
|
const userId = createUser();
|
|
expect(userId).toBeDefined();
|
|
});
|
|
it('should get user', () => {
|
|
const userId = createUser(); // Create fresh data
|
|
const user = getUser(userId);
|
|
expect(user).toBeDefined();
|
|
});
|
|
```
|
|
|
|
## CI/CD Integration
|
|
|
|
Tests run automatically on:
|
|
- Every push to feature branch
|
|
- Every pull request
|
|
- Before merge to `develop`
|
|
|
|
**Pipeline must pass** before merging.
|
|
|
|
## Debugging Tests
|
|
|
|
### Run Single Test
|
|
|
|
```typescript
|
|
it.only('should test specific case', () => {
|
|
// Only this test runs
|
|
});
|
|
```
|
|
|
|
### Skip Test
|
|
|
|
```typescript
|
|
it.skip('should test something', () => {
|
|
// This test is skipped
|
|
});
|
|
```
|
|
|
|
### Verbose Output
|
|
|
|
```bash
|
|
pnpm test --reporter=verbose
|
|
```
|
|
|
|
### Debug in VS Code
|
|
|
|
Add to `.vscode/launch.json`:
|
|
```json
|
|
{
|
|
"type": "node",
|
|
"request": "launch",
|
|
"name": "Vitest",
|
|
"runtimeExecutable": "pnpm",
|
|
"runtimeArgs": ["test", "--no-coverage"],
|
|
"console": "integratedTerminal"
|
|
}
|
|
```
|
|
|
|
## Test Database
|
|
|
|
Use separate test database:
|
|
|
|
```bash
|
|
# .env.test
|
|
TEST_DATABASE_URL=postgresql://mosaic:mosaic@localhost:5432/mosaic_test
|
|
|
|
# Reset test DB before each run
|
|
pnpm test:db:reset
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
- **Review Commit Guidelines** — [Committing](3-committing.md)
|
|
- **Learn Database Testing** — [Database](../2-database/3-prisma.md)
|
|
- **Check Code Style** — [Google TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html)
|