--- trigger: model_decision description: When writing tests, organizing test files, implementing test doubles, or setting up testing infrastructure --- ## Testing Strategy ### Test Pyramid **Unit Tests (70% of tests):** - **What:** Test domain logic in isolation with mocked dependencies - **Speed:** Fast (<100ms per test) - **Scope:** Single function, class, or module - **Dependencies:** All external dependencies mocked (repositories, APIs, time, random) - **Coverage Goal:** >85% of domain logic **Integration Tests (20% of tests):** - **What:** Test adapters against real infrastructure - **Speed:** Medium (100ms-5s per test) - **Scope:** Component interaction with infrastructure (database, cache, message queue) - **Dependencies:** Real infrastructure via Testcontainers - **Coverage Goal:** All adapter implementations, critical integration points **End-to-End Tests (10% of tests):** - **What:** Test complete user journeys through all layers - **Speed:** Slow (5s-30s per test) - **Scope:** Full system from HTTP request to database and back - **Dependencies:** Entire system running (or close approximation) - **Coverage Goal:** Happy paths, critical business flows ### Test-Driven Development (TDD) **Red-Green-Refactor Cycle:** 1. **Red:** Write a failing test for next bit of functionality 2. **Green:** Write minimal code to make test pass 3. **Refactor:** Clean up code while keeping tests green 4. **Repeat:** Next test **Benefits:** - Tests written first ensure testable design - Comprehensive test coverage (code without test doesn't exist) - Faster development (catch bugs immediately, not in QA) - Better design (forces thinking about interfaces before implementation) ### Test Doubles Strategy **Unit Tests:** Use mocks/stubs for all driven ports - Mock repositories return pre-defined data - Mock external APIs return successful responses - Mock time/random for deterministic tests - Control test environment completely **Integration Tests:** Use real infrastructure - Testcontainers spins up PostgreSQL, Redis, message queues - Firebase emulator spins up Firebase Authentication, Cloud Firestore, Realtime Database, Cloud Storage for Firebase, Firebase Hosting, Cloud Functions, Pub/Sub, and Firebase Extensions - Test actual database queries, connection handling, transactions - Verify adapter implementations work with real services **Best Practice:** - Generate at least 2 implementations per driven port: 1. Production adapter (PostgreSQL, GCP GCS, etc.) 2. Test adapter (in-memory, fake implementation) ### Test Organization **Universal Rule: Co-locate implementation tests; Separate system tests.** **1. Unit & Integration Tests (Co-located)** - **Rule:** Place tests **next to the file** they test. - **Why:** Keeps tests visible, encourages maintenance, and supports refactoring (moving a file moves its tests). - **Naming Convention Example:** - **TS/JS:** `*.spec.ts` (Unit), `*.integration.spec.ts` (Integration) - **Go:** `*_test.go` (Unit), `*_integration_test.go` (Integration) - **Dart/Flutter:** `*_test.dart` (Unit), `*_integration_test.dart` (Integration) — tests live in `test/` mirroring `lib/` layout (Flutter's default convention, discovered by `flutter test`) - **Python:** `test_*.py` (Unit), `test_*_integration.py` (Integration) - **Java:** `*Test.java` (Unit), `*IT.java` (Integration) - **Rust:** `#[cfg(test)] mod tests` inline in each `.rs` file (Unit), `tests/` directory at crate root (Integration) > You must strictly follow the convention for the target language. Do not mix `test` and `spec` suffixes in the same application context. > **Language-specific overrides:** Some ecosystems have different test location conventions. When a language-specific project structure file exists (e.g., `project-structure-flutter-mobile.md`, `project-structure-rust-cargo.md`), its test location rules take precedence over the co-location default. See `architectural-pattern.md` § Test co-location for the authoritative rule. > **Rust exception:** Rust unit tests are **inline** (`#[cfg(test)] mod tests` at the bottom of each `.rs` file), not separate files. This is the official Rust convention — it enables testing private functions and is how `cargo test` discovers unit tests. Integration tests go in `tests/` at the crate root, compiled as separate crates. See `rust-idioms-and-patterns.md` for details. **2. End-to-End Tests (Separate)** - **Rule:** Place in a dedicated `e2e/` folder - **Single app:** `e2e/` at project root - **Monorepo:** `apps/e2e/` subdivided by test scope: - `apps/e2e/api/` for full API flow E2E tests (HTTP → Database) - `apps/e2e/ui/` for full-stack E2E tests (Browser → Backend → Database) > Path adapts to project type per `project-structure.md` § Adapting for Different Project Types. - **Naming:** Follow `{feature}-{ui/api}.e2e.test.{ext}` (Universal - configure test runner to match this pattern `**/*.e2e.test.*`) - Example: - `user-registration-api.e2e.test.ts` # Full API flow: HTTP → DB - `user-registration-ui.e2e.test.ts` # Full-stack: Browser → Backend → DB **Using Playwright MCP for UI E2E Tests:** When running E2E tests interactively (during development or verification), use Playwright MCP: ``` # Navigate to the page mcp_playwright_browser_navigate(url="http://localhost:5173/login") # Capture accessible state (better than screenshot for assertions) mcp_playwright_browser_snapshot() # Interact with elements by ref from snapshot mcp_playwright_browser_type(ref="", text="test@example.com") mcp_playwright_browser_click(ref="") # Wait for results mcp_playwright_browser_wait_for(text="Welcome") # Capture snapshot for walkthrough documentation (captures full accessibility tree) mcp_playwright_browser_snapshot(filename="login-success.md") ``` **E2E Test Requirements:** - Capture a snapshot (`mcp_playwright_browser_snapshot`) at each major step - Save snapshots to walkthrough as proof of functionality - If visual proof is needed, use `browser_subagent` with `RecordingName` to produce a recorded video artifact - Test happy path AND at least one error path - Clean up test data after test (or use unique identifiers) **Key Principles:** - **Unit/Integration tests**: Co-located with implementation - **E2E tests**: Separate directory (crosses boundaries) - **Test doubles**: Co-located with interface (storage_mock.go, taskAPI.mock.ts) - **Pattern consistency**: All features follow same structure ### Test Quality Standards **AAA Pattern (Arrange-Act-Assert):** ``` // Arrange: Set up test data and mocks const user = { id: '123', email: 'test@example.com' }; const mockRepo = createMockRepository(); // Act: Execute the code under test const result = await userService.createUser(user); // Assert: Verify expected outcome expect(result.id).toBe('123'); expect(mockRepo.save).toHaveBeenCalledWith(user); ``` **Test Naming:** - Descriptive: `should [expected behavior] when [condition]` - Examples: - `should return 404 when user not found` - `should hash password before saving to database` - `should reject email with invalid format` **Coverage Requirements:** - Unit tests: >85% code coverage - Integration tests: All adapter implementations - E2E tests: Critical user journeys ### Related Principles - Architectural Patterns — Testability-First Design @architectural-pattern.md - Error Handling Principles @error-handling-principles.md - Project Structure @project-structure.md