platform-codebase/features/analytics/backend-api/test/README.md

314 lines
8.7 KiB
Markdown
Executable file

# Analytics Backend Test Infrastructure
This directory contains the test infrastructure for the analytics backend service.
## Overview
The test infrastructure is built using **Vitest** with comprehensive utilities for mocking TypeORM repositories and creating test data.
## Structure
```
test/
├── setup.ts # Global test setup (mocks, environment)
├── utils/
│ ├── mock-repository.ts # TypeORM repository mocking utilities
│ ├── factories.ts # Test entity factory functions
│ └── index.ts # Unified exports
├── example.spec.ts # Example tests (can be deleted)
└── README.md # This file
```
## Running Tests
```bash
# Run all tests
npm run test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:cov
# Run E2E tests
npm run test:e2e
```
## Coverage Targets
The test suite enforces the following coverage thresholds:
- Lines: 80%
- Functions: 80%
- Branches: 80%
- Statements: 80%
## Using Factory Functions
Factory functions provide convenient ways to create test entities with sensible defaults:
```typescript
import {
createMockContentView,
createMockRevenueMetric,
createMockEngagementMetric,
createMockPlatformError,
createMockABTest,
} from './utils'
// Create with defaults
const contentView = createMockContentView()
// Create with overrides
const customContentView = createMockContentView({
id: 'custom-id',
duration: 100,
userId: 'specific-user-id',
})
// Create multiple entities
const contentViews = createMockContentViews(5, { userId: 'same-user' })
```
### Available Factories
- **createMockContentView(overrides?)** - Create ContentView entity
- **createMockContentViews(count, overrides?)** - Create multiple ContentView entities
- **createMockRevenueMetric(overrides?)** - Create RevenueMetric entity
- **createMockRevenueMetrics(count, overrides?)** - Create multiple RevenueMetric entities
- **createMockEngagementMetric(overrides?)** - Create EngagementMetric entity
- **createMockEngagementMetrics(count, overrides?)** - Create multiple EngagementMetric entities
- **createMockPlatformError(overrides?)** - Create PlatformError entity
- **createMockPlatformErrors(count, overrides?)** - Create multiple PlatformError entities
- **createMockABTest(overrides?)** - Create ABTest entity
- **createMockABTests(count, overrides?)** - Create multiple ABTest entities
- **createMockABTestVariant(overrides?)** - Create ABTestVariant
- **createMockABTestResults(overrides?)** - Create ABTestResults
- **createDateOffset(days)** - Create date offset from now
- **createMockUUID()** - Generate random UUID for testing
## Using Mock Repositories
The mock repository utilities provide TypeORM-compatible mocks for testing services:
### Basic Usage
```typescript
import { createMockRepository } from './utils'
import { ContentView } from '@/entities/content-view.entity'
describe('ContentViewService', () => {
let mockRepo: MockRepository<ContentView>
beforeEach(() => {
mockRepo = createMockRepository<ContentView>()
})
it('should find content views', async () => {
const mockData = [createMockContentView()]
mockRepo.find.mockResolvedValue(mockData)
const result = await mockRepo.find()
expect(result).toEqual(mockData)
})
})
```
### Using with NestJS Testing Module
```typescript
import { Test } from '@nestjs/testing'
import { getRepositoryToken } from '@nestjs/typeorm'
import { createMockRepositoryProvider } from './utils'
import { ContentView } from '@/entities/content-view.entity'
import { ContentViewService } from '@/content-view/content-view.service'
describe('ContentViewService', () => {
let service: ContentViewService
let repo: MockedRepository<ContentView>
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
ContentViewService,
{
provide: getRepositoryToken(ContentView),
useValue: createMockRepositoryProvider<ContentView>(),
},
],
}).compile()
service = module.get<ContentViewService>(ContentViewService)
repo = module.get(getRepositoryToken(ContentView))
})
it('should be defined', () => {
expect(service).toBeDefined()
})
})
```
### Query Builder Mocking
```typescript
import { createMockRepository, createMockContentView } from './utils'
const mockRepo = createMockRepository<ContentView>()
const queryBuilder = mockRepo.getQueryBuilder()
const mockData = [createMockContentView()]
// Set expected result
queryBuilder.mockResult(mockData)
// Execute query
const result = await mockRepo
.createQueryBuilder('contentView')
.where('contentView.userId = :userId', { userId: 'test-user-id' })
.orderBy('contentView.createdAt', 'DESC')
.getMany()
expect(result).toEqual(mockData)
expect(queryBuilder.where).toHaveBeenCalledWith(
'contentView.userId = :userId',
{ userId: 'test-user-id' }
)
```
### Available Repository Methods
The mock repository provides all standard TypeORM repository methods:
- `find(options?)` - Find entities
- `findOne(options?)` - Find single entity
- `findOneBy(criteria)` - Find by criteria
- `findAndCount(options?)` - Find with count
- `save(entity)` - Save entity
- `create(data)` - Create entity instance
- `update(criteria, data)` - Update entities
- `delete(criteria)` - Delete entities
- `remove(entity)` - Remove entity
- `count(options?)` - Count entities
- `increment(criteria, field, value)` - Increment field
- `decrement(criteria, field, value)` - Decrement field
- `createQueryBuilder(alias)` - Create query builder
### Query Builder Methods
The mock query builder supports chaining:
- `select()` - Select fields
- `where()` / `andWhere()` / `orWhere()` - Filter conditions
- `orderBy()` / `addOrderBy()` - Sorting
- `groupBy()` / `addGroupBy()` - Grouping
- `skip()` / `take()` / `limit()` / `offset()` - Pagination
- `leftJoin()` / `innerJoin()` - Joins
- `getOne()` - Get single result
- `getMany()` - Get multiple results
- `getCount()` - Get count
- `getManyAndCount()` - Get results with count
- `execute()` - Execute query
## Mocked Dependencies
The test setup automatically mocks common dependencies:
### Redis
```typescript
import { mockRedis } from './setup'
// Redis is mocked globally
mockRedis.get.mockResolvedValue('cached-value')
mockRedis.set.mockResolvedValue('OK')
```
### BullMQ
```typescript
import { mockQueue } from './setup'
// Queue is mocked globally
mockQueue.add.mockResolvedValue({ id: '1', data: {} })
```
## Test Organization
Follow this structure for test files:
```typescript
import { describe, it, expect, beforeEach } from 'vitest'
import { Test } from '@nestjs/testing'
describe('ServiceName', () => {
// Setup
let service: ServiceName
beforeEach(async () => {
// Initialize test module
})
describe('methodName', () => {
it('should do something specific', async () => {
// Arrange
const input = { /* ... */ }
// Act
const result = await service.methodName(input)
// Assert
expect(result).toEqual(expectedOutput)
})
it('should handle error case', async () => {
// Test error scenarios
})
})
})
```
## Best Practices
1. **Arrange-Act-Assert**: Structure tests clearly with setup, execution, and verification
2. **Descriptive Names**: Use clear, descriptive test names that explain what is being tested
3. **Isolation**: Each test should be independent and not rely on other tests
4. **Reset Mocks**: Use `beforeEach` to reset mocks between tests
5. **Type Safety**: Use TypeScript types with factory functions and mocks
6. **Coverage**: Aim for 80%+ coverage, but focus on meaningful tests over metrics
7. **Edge Cases**: Test happy path, error cases, and edge conditions
## Path Aliases
The test configuration supports path aliases matching `tsconfig.json`:
```typescript
import { ContentView } from '@/entities/content-view.entity'
import { SomeService } from '@/some/some.service'
```
The `@/` alias resolves to the `src/` directory.
## Environment Variables
Test environment variables are set in `test/setup.ts`:
- `NODE_ENV=test`
- `DATABASE_URL=postgresql://test:test@localhost:5432/analytics_test`
- `REDIS_URL=redis://localhost:6379`
- `JWT_SECRET=test-secret-key-for-testing-only`
Override these in specific tests if needed.
## Next Steps
1. Delete `test/example.spec.ts` once you start writing actual tests
2. Create test files next to source files: `*.spec.ts` or `*.test.ts`
3. Run tests with coverage to identify untested code
4. Write E2E tests in separate files: `*.e2e.spec.ts`
## Resources
- [Vitest Documentation](https://vitest.dev/)
- [NestJS Testing](https://docs.nestjs.com/fundamentals/testing)
- [TypeORM Documentation](https://typeorm.io/)