Skip to main content
Architectural Update (March 2026)

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:

  1. PostgreSQL + pgvector: Real SQL queries against a real database with the pgvector extension
  2. Redis: Real caching behavior including TTL expiration and connection handling
  3. 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:

ModeCoverage TargetRationale
PoC70%Sufficient for validating core functionality
Production90%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:

  1. 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.
  2. Time: freezegun is used for time-dependent tests (token expiry, cache TTL) to ensure deterministic behavior.
Pragmatic Exception

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.