A social knowledge tool for researchers built on ATProto
1# Testing Strategy 2 3This document outlines the testing strategy for the Annos service, following Domain-Driven Design (DDD) and Layered Architecture principles. 4 5## Goals 6 7- Ensure correctness and reliability of the application. 8- Provide fast feedback during development. 9- Facilitate refactoring and maintenance. 10- Verify integration between different parts of the system. 11 12## Testing Levels 13 14We will employ a multi-layered testing approach, focusing tests at the appropriate level of the architecture: 15 161. **Unit Tests:** 17 - **Focus:** Individual classes, methods, or functions in isolation. Primarily target Domain Layer objects (Aggregates, Value Objects, Domain Services) and utility functions. 18 - **Scope:** Test business logic, validation rules, state transitions, and calculations within a single unit. 19 - **Dependencies:** Mocked or stubbed. No external dependencies (database, network, file system). 20 - **Tools:** Jest. 21 - **Location:** `tests/<bounded-context>/domain/**` (e.g., `tests/annotations/domain/value-objects/AnnotationValue.test.ts`) 22 232. **Integration Tests:** 24 - **Focus:** Interactions between components within a layer or across adjacent layers. Primarily target Application Layer Use Cases and Infrastructure Layer components (Repositories, Mappers). 25 - **Scope:** 26 - _Application Layer:_ Verify use cases correctly orchestrate domain objects and interact with repositories (using mocked repositories). 27 - _Infrastructure Layer:_ Verify repositories correctly interact with the database (using a test database or in-memory alternatives if feasible), or mappers correctly transform data. 28 - **Dependencies:** May involve mocked components (e.g., mocking a repository for a use case test) or real infrastructure components connected to a test environment (e.g., testing a repository against a test database). 29 - **Tools:** Jest, Test Database (e.g., Dockerized Postgres), potentially Supertest for API endpoint integration. 30 - **Location:** `tests/<bounded-context>/application/**`, `tests/<bounded-context>/infrastructure/**` (e.g., `tests/annotations/application/use-cases/CreateAnnotationUseCase.test.ts`, `tests/annotations/infrastructure/persistence/AnnotationRepository.integration.test.ts`) 31 323. **End-to-End (E2E) Tests:** 33 - **Focus:** Simulating real user scenarios through the entire system, typically via the API or UI. 34 - **Scope:** Verify complete workflows, from request initiation (e.g., HTTP request) to response validation, including interactions with the database and potentially external services. 35 - **Dependencies:** Requires a fully running instance of the application and its dependencies (database, etc.) in a dedicated test environment. 36 - **Tools:** Supertest (for API testing), potentially Playwright or Cypress if a UI is involved. 37 - **Location:** `tests/e2e/**` 38 39## Guiding Principles 40 41- **Test Pyramid:** Emphasize a larger number of fast unit tests, a moderate number of integration tests, and fewer, more comprehensive E2E tests. 42- **Isolate Layers:** Test domain logic independently of application and infrastructure concerns. Test application logic with mocked infrastructure. Test infrastructure against real (test) dependencies. 43- **Mocking:** Use mocking judiciously, primarily at the boundaries between layers (e.g., mocking repositories in use case tests). Avoid excessive mocking within a single unit. 44- **Test Data:** Use realistic and clearly defined test data. Consider factories or builders for creating complex objects. 45- **CI/CD:** Integrate tests into the Continuous Integration pipeline to ensure code quality and prevent regressions. 46 47## Test Runner & Environment 48 49- **Runner:** Jest 50- **Configuration:** `jest.config.js` (to be created if needed for more complex setup). 51- **Execution:** `npm test` 52 53## Current Focus 54 55Initially, we will focus on: 56 57- Unit tests for critical Domain Layer Value Objects and Aggregates. 58- Integration tests for Application Layer Use Cases, mocking the repository layer. 59 60## Testing Repository Implementations 61 62Repository implementations like `DrizzleAnnotationFieldRepository` and `DrizzleAnnotationTemplateRepository` require special consideration as they interact with databases: 63 64### Approach 1: In-memory Database (Recommended for Unit Tests) 65 66For fast, isolated tests of repository implementations: 67 681. **Use SQLite in-memory database:** 69 70 ```typescript 71 import { drizzle } from 'drizzle-orm/better-sqlite3'; 72 import Database from 'better-sqlite3'; 73 import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; 74 75 // Setup in-memory SQLite for tests 76 const sqlite = new Database(':memory:'); 77 const db = drizzle(sqlite); 78 79 // Apply migrations or create schema 80 migrate(db, { migrationsFolder: './drizzle' }); 81 82 // Create repository with in-memory database 83 const repo = new DrizzleAnnotationFieldRepository(db); 84 ``` 85 862. **Benefits:** 87 - Fast execution - no network or disk I/O 88 - Isolated - each test gets a fresh database 89 - No external dependencies or setup required 90 913. **Limitations:** 92 - SQLite dialect differences from PostgreSQL 93 - Some PostgreSQL-specific features won't be testable 94 95### Approach 2: Test Containers (For Integration Tests) 96 97For more realistic tests that verify PostgreSQL compatibility: 98 991. **Use testcontainers-node to spin up a PostgreSQL container:** 100 101 ```typescript 102 import { PostgreSqlContainer } from 'testcontainers'; 103 import postgres from 'postgres'; 104 import { drizzle } from 'drizzle-orm/postgres-js'; 105 106 // In beforeAll hook 107 const container = await new PostgreSqlContainer().start(); 108 const connectionString = container.getConnectionUri(); 109 const sql = postgres(connectionString); 110 const db = drizzle(sql); 111 112 // In afterAll hook 113 await container.stop(); 114 ``` 115 1162. **Benefits:** 117 - Tests against actual PostgreSQL 118 - Verifies dialect-specific features 119 - Isolated from development/production databases 120 1213. **Drawbacks:** 122 - Slower test execution 123 - Requires Docker 124 125### Approach 3: Mock Database Client (For Pure Unit Tests) 126 127For pure unit tests focusing on repository logic: 128 1291. **Mock the database client:** 130 131 ```typescript 132 const mockDb = { 133 select: jest.fn().mockReturnThis(), 134 from: jest.fn().mockReturnThis(), 135 where: jest.fn().mockReturnThis(), 136 limit: jest.fn().mockReturnValue([ 137 { 138 /* mock data */ 139 }, 140 ]), 141 insert: jest.fn().mockReturnThis(), 142 values: jest.fn().mockReturnThis(), 143 onConflictDoUpdate: jest.fn().mockResolvedValue(undefined), 144 delete: jest.fn().mockReturnThis(), 145 }; 146 147 const repo = new DrizzleAnnotationFieldRepository(mockDb as any); 148 ``` 149 1502. **Benefits:** 151 - Fastest execution 152 - No database dependencies 153 - Tests repository logic in isolation 154 1553. **Drawbacks:** 156 - Doesn't test actual SQL generation 157 - Requires complex mocking setup 158 - Lower confidence in database interaction 159 160### Recommended Approach 161 162Use a combination: 163 1641. **Unit tests with SQLite in-memory** for fast feedback during development 1652. **Integration tests with test containers** for critical paths and PostgreSQL-specific features 1663. **End-to-end tests** that verify the entire stack works together 167 168This provides a balance of speed, isolation, and confidence in your repository implementations.