aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--languages/python/claude/rules/python-testing.md15
1 files changed, 13 insertions, 2 deletions
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