|
1 | | -# Pattern 1.5 — No-Mock Philosophy |
| 1 | +# Pattern 1.5 — Real Dependencies in E2E/Integration and Stack Tests |
2 | 2 |
|
3 | 3 | ## Problem |
4 | 4 |
|
5 | | -Mock system components and you test your mocks, not your system. Mocks lie: they return perfect data, never timeout, never throw unexpected errors. Tests pass but production fails because real databases have latency, real APIs return errors, real caches miss. Mocking creates a fantasy system that doesn't exist. |
| 5 | +Mock system components in end-to-end tests and you test your mocks, not your system. Mocks lie: they return perfect data, never timeout, never throw unexpected errors. Tests pass but production fails because real databases have latency, real APIs return errors, real caches miss. Mocking in E2E and stack tests creates a fantasy system that doesn't exist. |
| 6 | + |
| 7 | +This principle applies to **stack tests and E2E/integration tests only** — tests that verify system behavior through the API. Unit tests are a different concern: they validate individual module contracts in isolation, where mocks provide the necessary isolation to test logic without standing up infrastructure. See [Pattern 0.5 — Unit Tests as Contract](../L0-foundation.md#pattern-05--unit-tests-as-contract). |
6 | 8 |
|
7 | 9 | ## Solution |
8 | 10 |
|
9 | | -**Stack tests use real everything** — real PostgreSQL, real Redis, real RabbitMQ, real HTTP calls to other services. The only acceptable mocks are external services you don't control: third-party APIs where no sandbox exists, payment processors where test mode is unavailable. |
| 11 | +**Stack tests and E2E/integration tests use real everything** — real PostgreSQL, real Redis, real RabbitMQ, real HTTP calls to other services. The only acceptable mocks in these tests are external services you don't control: third-party APIs where no sandbox exists, payment processors where test mode is unavailable. |
| 12 | + |
| 13 | +Real dependencies philosophy: if you own it, run it. If you can run it in Docker, run it in Docker. If you can't, that's a deployment dependency, not a testing concern. |
10 | 14 |
|
11 | | -No-mock philosophy: if you own it, run it. If you can run it in Docker, run it in Docker. If you can't, that's a deployment dependency, not a testing concern. |
| 15 | +**Mocks in unit tests are appropriate and encouraged.** Unit tests validate module contracts — how a function handles edge cases, error paths, and boundary conditions. Mocks provide the isolation that makes unit tests fast, focused, and diagnostic. See [Pattern 0.5 — Unit Tests as Contract](../L0-foundation.md#pattern-05--unit-tests-as-contract) for the unit test perspective. |
12 | 16 |
|
13 | 17 | ## In Practice |
14 | 18 |
|
15 | | -What to mock vs. not mock: |
| 19 | +What to mock vs. not mock — this table applies to stack tests and E2E/integration tests: |
16 | 20 |
|
17 | | -| Component | Mock? | Reason | |
18 | | -|-----------|-------|--------| |
19 | | -| PostgreSQL, MySQL, MongoDB | No | Run in Docker — free, fast, realistic | |
20 | | -| Redis, Memcached | No | Run in Docker — trivial setup | |
21 | | -| RabbitMQ, Kafka | No | Run in Docker — handles real edge cases | |
22 | | -| Internal microservices | No | Run the full stack — integration is what you're testing | |
23 | | -| External APIs with sandbox (Stripe, Plaid) | No | Use sandbox — they provide it for this reason | |
24 | | -| External APIs without sandbox | Yes | Mock the client, test error handling | |
25 | | -| Time (for testing expiry) | Maybe | Use time-skewing libraries if system clock dependency is critical | |
| 21 | +| Component | Mock in Stack/E2E? | Mock in Unit? | Reason | |
| 22 | +|-----------|-------------------|---------------|--------| |
| 23 | +| PostgreSQL, MySQL, MongoDB | No | Yes | Stack: run in Docker. Unit: mock for isolation. | |
| 24 | +| Redis, Memcached | No | Yes | Stack: run in Docker. Unit: mock for isolation. | |
| 25 | +| RabbitMQ, Kafka | No | Yes | Stack: run in Docker. Unit: mock for isolation. | |
| 26 | +| Internal microservices | No | Yes | Stack: run the full stack. Unit: mock to test module logic. | |
| 27 | +| External APIs with sandbox (Stripe, Plaid) | No | Yes | Stack: use sandbox. Unit: mock for isolation. | |
| 28 | +| External APIs without sandbox | Yes | Yes | No test environment available at any level. | |
| 29 | +| Time (for testing expiry) | Maybe | Yes | Stack: time-skewing libraries. Unit: mock freely. | |
26 | 30 |
|
27 | | -Example: Testing a payment flow |
| 31 | +Example: Testing a payment flow in a stack test |
28 | 32 |
|
29 | 33 | ```typescript |
30 | | -// Good: Use Stripe testnet |
| 34 | +// Good: Use Stripe testnet in stack tests |
31 | 35 | const stripe = new Stripe(process.env.STRIPE_TEST_KEY); |
32 | 36 | const payment = await stripe.paymentIntents.create({ |
33 | 37 | amount: 1000, |
34 | 38 | currency: 'usd', |
35 | 39 | // Real Stripe testnet handles edge cases: declined cards, network errors |
36 | 40 | }); |
37 | 41 |
|
38 | | -// Bad: Mock Stripe client |
| 42 | +// Bad: Mock Stripe client in a stack test |
39 | 43 | const mockStripe = { |
40 | 44 | paymentIntents: { |
41 | 45 | create: () => ({ id: 'pi_mock', status: 'succeeded' }) |
42 | 46 | }; |
43 | 47 | // This passes tests but tells you nothing about real integration |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +Example: Mocking in a unit test is appropriate |
| 52 | + |
| 53 | +```typescript |
| 54 | +// Good: Mock the Stripe client in a unit test to isolate payment logic |
| 55 | +const mockStripe = { |
| 56 | + paymentIntents: { |
| 57 | + create: vi.fn().mockResolvedValue({ id: 'pi_test', status: 'succeeded' }) |
| 58 | + } |
44 | 59 | }; |
| 60 | +// Unit test validates how processPayment handles the response, |
| 61 | +// including edge cases like declined cards and network errors |
45 | 62 | ``` |
46 | 63 |
|
47 | 64 | ## Anti-Pattern |
48 | 65 |
|
49 | | -**Don't** mock databases "because they're slow." PostgreSQL in Docker adds ~2 seconds to startup. Mock databases to test complex queries in unit tests, not in stack tests. |
| 66 | +**Don't** mock databases in stack tests "because they're slow." PostgreSQL in Docker adds ~2 seconds to startup. That's the cost of real confidence. |
| 67 | + |
| 68 | +**Don't** mock external services that provide test environments in stack tests. Stripe, Plaid, Twilio, etc. all provide test/sandbox modes. Use them — they catch real integration bugs. |
50 | 69 |
|
51 | | -**Don't** mock external services that provide test environments. Stripe, Plaid, Twilio, etc. all provide test/sandbox modes. Use them — they catch real integration bugs. |
| 70 | +**Don't** mock for "determinism" in stack tests. Real systems are non-deterministic. You want tests to fail when race conditions exist, not hide them behind perfect mocks. |
52 | 71 |
|
53 | | -**Don't** mock for "determinism." Real systems are non-deterministic. You want tests to fail when race conditions exist, not hide them behind perfect mocks. |
| 72 | +**Don't** avoid mocks in unit tests out of misplaced consistency. Unit tests and stack tests serve different purposes — mocks provide isolation in unit tests, real dependencies provide confidence in stack tests. |
54 | 73 |
|
55 | 74 | ## Cross-References |
56 | 75 |
|
57 | | -- **[Pattern 1.1 — Stack Tests](1.1-stack-tests.md)**: No-mocks is core to stack test philosophy |
58 | | -- **[L0 — Unit Tests as Contract](../L0-foundation.md#pattern-05--unit-tests-as-contract)**: Unit tests validate individual module contracts in isolation — stack tests validate system behavior through the API |
59 | | -- **[L2 — Deterministic Simulation](../L2-behavioral-guardrails.md)**: When you truly need determinism (e.g., replay tests), use simulation, not mocks |
| 76 | +- **[Pattern 1.1 — Stack Tests](1.1-stack-tests.md)**: Real dependencies is core to stack test philosophy |
| 77 | +- **[L0 — Unit Tests as Contract](../L0-foundation.md#pattern-05--unit-tests-as-contract)**: Unit tests validate individual module contracts — mocks are appropriate for isolation |
| 78 | +- **[L2 — Constitutional Rules](../L2-behavioral-guardrails.md#pattern-24--constitutional-rules)**: Constitutional rule enforces real dependencies in stack tests specifically |
60 | 79 |
|
61 | 80 | --- |
62 | 81 |
|
|
0 commit comments