testing-patterns · git:20260618.bd16f67 · 2026-06-18 · sha256 71f795e1c298954a
testing-patterns git:20260618.bd16f67A
Immutable. This exact content never changes and is served at /api/v1/blob/71f795e1c298954a.
--- name: testing-patterns description: Integration test philosophy — test observable behavior, fixture composition, canary markers for library internals. when_to_use: Writing tests, reviewing test code, or deciding between unit and integration tests. user-invocable: false --- # Testing Philosophy ## Integration Tests Verify Behavior, Not Internals Integration tests must verify **observable behavior through endpoints**, not implementation details. **Good integration tests:** - Drive endpoints with an HTTP client - Assert status codes and response bodies - Verify side effects (emails sent, database rows created, audit entries logged) - Test cross-cutting concerns (middleware, auth, rate limiting) **Bad integration tests:** - Calling internal service functions for behavior already observable via HTTP - Using DB queries as the primary assertion surface (DB is for post-assertion verification) - Testing internal query functions or utility functions — those belong in unit tests - Duplicating coverage already exercised by another test ## Prefer Fixture Composition If a test starts with boilerplate setup (register a user, log in, configure a setting), that belongs in a fixture. Test bodies should stay focused on what they're actually testing. Build fixture stacks: `user` -> `logged_in_user` -> `admin_user`. Each level adds one concern. Tests request the level they need. ## Canary Markers for Library Internals When code depends on a library's private or poorly-documented internals, add a **canary test** that asserts the internal's shape directly — don't rewrite production code to avoid the dependency when doing so would cost real accuracy or capability. The canary turns an upstream rename or shape change into a loud test failure instead of a silent runtime bug. Canary practices: - Assert the specific attribute/method and the shape of its return value — not just "no exception raised" - Name the test so the failure message makes the breakage obvious (e.g., `test_library_rate_limit_contract`), since it'll likely be read by whoever just bumped a dep - Place it close to the module that uses the internal