Module-level DB pool creation blocks all unit tests
Module-level DB pool creation blocks all unit tests
Severity: P1 (broad test-suite breakage at collection time) · Applies to: any module that constructs a connection pool or other DB-dependent resource at import time Related: ADR-041 — Connection pooling · Connection pooling runbook · ADR-045 — Test Supabase instance lifecycle
Problem
Unit tests that don’t need a database fail at import time because connection.py creates a PostgreSQL connection pool as a module-level side effect. Any module that transitively imports from connection.py triggers the pool creation, which crashes if no database is available.
Symptoms:
psycopg2.OperationalError: connection to server at "localhost" ... refused- Tests fail during collection, not during execution
- Tests for pure domain logic (no DB needed) fail because they import modules that import modules that import
connection.py - Setting
DATABASE_URL“fixes” it, but couples unit tests to infrastructure
Root Cause
# connection.py — BEFORE (module-level side effect)_pool = psycopg2.pool.ThreadedConnectionPool( minconn=2, maxconn=20, dsn=DATABASE_URL)This line executes at import time. Python’s import system is eager — when any module in the import chain touches connection.py, the pool tries to connect immediately. In a Clean Architecture codebase, the import chain is deep:
test_surgical.py → optimization/surgical.py → evaluation/evaluator.py → infrastructure/persistence/connection.py ← BOOMSolution
Lazy initialization — create the pool on first use, not at import time.
# connection.py — AFTER (lazy initialization)_pool = None
def _get_pool(): """Get or create the connection pool (lazy initialization).""" global _pool if _pool is None: _pool = psycopg2.pool.ThreadedConnectionPool( minconn=2, maxconn=20, dsn=DATABASE_URL ) return _pool
def get_connection(): pool = _get_pool() # ← pool created here, not at import raw_conn = pool.getconn() ...Result: 66 → 87 tests passing. Zero tests require a live database unless they explicitly use the db fixture.
Prevention
Best Practices
- Never create connections, pools, or clients at module level
- Use lazy initialization or dependency injection for all infrastructure
- The
conftest.pydbfixture pattern: skip tests whenDATABASE_URLis not set
Warning Signs
- Tests failing during “collecting” phase (before any test runs)
ImportErrororOperationalErrorin test output pointing to infrastructure modules- Tests that “work on CI but not locally” (CI has a DB, local doesn’t)
Related Tests
# conftest.py — DB fixture that skips gracefully@pytest.fixturedef db(): if not os.environ.get("DATABASE_URL"): pytest.skip("DATABASE_URL not set") from spectral.infrastructure.persistence.connection import get_db conn = get_db() yield conn conn.rollback()References
- Commit
bdee705— lazy pool init + test suite expansion - Related: Clean Architecture principle — infrastructure should never leak into domain/application imports