Kuldeep Pisda — Django & growth consultant · creator of django-rls · DjangoCon US tutorials 2022–24 · kdpisda.in
| SITUATION | REACH FOR | WHY |
|---|---|---|
| Test never touches the DB | build() / build_batch() | In-memory, no INSERT — the fast default for pure logic. |
| Model needs a parent FK | SubFactory | The dependency must exist before the child row. |
| Parent should spawn children | RelatedFactory | Runs after creation — reverse relations without loops. |
| Field derived from siblings | LazyAttribute | lambda o: f"{o.username}@corp.dev" |
| Circular FKs | string ref + SelfAttribute | Break one direction — never two eager SubFactories. |
FAKER RULE Match the provider to the domain — faker.company(), not "test1". Failure messages should read like your data.
Django apps stack three permission layers — model (Meta.permissions), view (permission_classes), object (django-guardian / RLS). Test each layer once in isolation, then test the role × action matrix at the view with one parameterized test instead of thirty functions:
| ROLE | list | retrieve | update | delete |
|---|---|---|---|---|
| anonymous | 401 | 401 | 401 | 401 |
| member | 200 | 200 | 403 | 403 |
| staff | 200 | 200 | 200 | 403 |
| owner | 200 | 200 | 200 | 204 |
Roles come from factory traits — one factory, four personas, zero JSON fixtures.
“Mock at the boundary where your code meets code you don’t own.”
| ANTI-PATTERN | THE SMELL | THE FIX |
|---|---|---|
| The Leaky Test | on_commit hooks silently never fire under TestCase. | Use TransactionTestCase when the code manages transactions. |
| Over-Mocking | assert_called_once_with everywhere — tests pin implementation. | Assert behavior: assert order.total == Decimal("21.60"). |
| Fixture Graveyard | JSON fixture files nobody dares touch; every migration breaks them. | Factories declare only what the test needs: OrgFactory(plan="pro"). |
| Order-Dependent Tests | Module-level state; test_b passes only after test_a. | State lives in fixtures; enforce with pytest-randomly in CI. |
| The God Factory | Default factory creates org, plan, team, invoice… half the schema per test. | Opt in with traits: UserFactory(with_org=True). |
Kuldeep Pisda consults on production Django systems — multi-tenant SaaS, 50k+ concurrent-user backends, PostgreSQL performance recovery, and test suites teams actually trust.