This ADR was written when the system used Neo4j for entity storage. As of March 2026, Neo4j has been fully removed and replaced by PostgreSQL taxonomy tables (taxonomy_entities, taxonomy_relationships). The decision rationale documented here remains valid; the storage layer has changed.
ADR-0002: No Mocking Policy
Status: Accepted
Context
Automated testing is essential for maintaining system quality, but the type of testing matters as much as the quantity. In a system that integrates PostgreSQL, Redis, Neo4j, and external LLM APIs, mocked tests risk testing the mock implementation rather than the actual integration behavior.
Common mocking pitfalls observed in similar projects:
- A mocked database always returns results in the expected format -- the real database may return unexpected types or null values
- A mocked Redis always responds instantly -- the real Redis may timeout or lose connection
- A mocked LLM always returns well-formed JSON -- the real LLM may return malformed responses or timeout
These false assurances lead to tests that pass in CI but fail in production -- precisely the scenario testing should prevent.
Decision
Adopt a no-mocking policy for integration tests. Use Testcontainers to spin up real database instances (PostgreSQL with pgvector, Redis) in Docker containers for every test run.
Testcontainers Architecture
Testcontainers creates ephemeral Docker containers for each test session:
- PostgreSQL + pgvector: Real SQL queries against a real database with the pgvector extension
- Redis: Real caching behavior including TTL expiration and connection handling
- Neo4j: Real Cypher queries against a real graph database (for integration tests)
Each test session gets a fresh database, ensuring test isolation without the overhead of manual setup/teardown.
Coverage Requirements
Following the Golden Standard v6 framework:
| Mode | Coverage Target | Rationale |
|---|---|---|
| PoC | 70% | Sufficient for validating core functionality |
| Production | 90% | Comprehensive coverage for production deployment |
Consequences
Positive
- Real integration testing: Tests catch actual database behavior, connection issues, and query quirks
- No mock maintenance: Mocks require updating when the underlying service changes; Testcontainers use the actual service
- Higher confidence: Passing tests indicate genuine compatibility, not mock compliance
- Catches real issues: Connection timeouts, type mismatches, and SQL syntax errors are caught before deployment
Negative
- Slower tests: Starting Docker containers adds ~30-60 seconds to test initialization. Full test suite runs ~5 minutes vs. ~30 seconds with mocks.
- Docker dependency: CI/CD environments must support Docker. Local development requires Docker Desktop.
- Resource usage: Running multiple containers simultaneously requires significant memory (~2GB for all containers)
- Flakiness potential: Docker container startup can occasionally fail, requiring retry logic in CI pipelines
Test Pyramid
What Is Still Mocked
The no-mocking policy applies to infrastructure dependencies (databases, caches). Two categories are still mocked:
- External LLM APIs: OpenAI API calls are mocked because they are expensive, non-deterministic, and rate-limited. A dedicated integration test suite with real LLM calls runs weekly rather than on every commit.
- Time:
freezegunis used for time-dependent tests (token expiry, cache TTL) to ensure deterministic behavior.
The no-mocking policy is a guideline, not a dogma. When mocking is the pragmatically correct choice (costly external APIs, non-deterministic outputs), it is permitted with explicit documentation of why the real service is not used.