From 7f17f6116f846ce0817da2caca4855cc7e72d576 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 22 May 2026 15:07:32 -0500 Subject: docs(languages): revise python-testing SQLite and ORM-mocking guidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two audit fixes. The "prefer in-memory SQLite" advice is risky when prod is Postgres or MySQL — SQLite diverges on query semantics, constraints, transactions, JSON, time zones, and indexes, so a test can pass on SQLite and fail in prod. ORM/query tests now use a production-like DB, with SQLite reserved for pure unit tests. The "never mock the ORM" rule is also clarified: don't mock ORM internals, but a thin orchestration unit can inject a fake at a deliberate data-access port it owns. --- languages/python/claude/rules/python-testing.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'languages/python/claude') diff --git a/languages/python/claude/rules/python-testing.md b/languages/python/claude/rules/python-testing.md index d2342c1..4edde35 100644 --- a/languages/python/claude/rules/python-testing.md +++ b/languages/python/claude/rules/python-testing.md @@ -92,12 +92,18 @@ to skip. - Email sending (Django: `django.core.mail.outbox`) ### Never mock these (internal domain): -- ORM queries (SQLAlchemy, Django ORM) +- ORM internals (querysets, sessions, model internals) - Model methods and properties - Form and serializer validation - Middleware - Your own service functions +For domain services, use real model methods and validation — don't mock the +ORM. But a thin orchestration unit can be cleaner to test with a fake injected +at a deliberate data-access port (a repository or interface the code owns). +That's still mocking at a boundary, not at internals: replace the port you +defined, not the ORM's querysets, sessions, or model internals underneath it. + ## Async Testing Use `anyio` for async tests (not raw `asyncio`): @@ -113,5 +119,10 @@ async def test_process_order_async(): - Mark database tests with `@pytest.mark.django_db` - Use transactions for isolation (pytest-django default) -- Prefer in-memory SQLite for speed in unit tests +- Run ORM/query tests against a production-like database — the same engine + as prod (a test Postgres or MySQL, often containerized). SQLite differs + from Postgres/MySQL on query semantics, constraints, transactions, JSON + columns, time zones, and indexes, so a test can pass on SQLite and fail + in production. Reserve in-memory SQLite for pure unit tests that don't + depend on database semantics. - Use `select_related` / `prefetch_related` assertions to catch N+1 regressions -- cgit v1.2.3