The Test Pyramid Is a Lie: Modern Layering
The 'lots of unit tests, fewer integration, fewer E2E' diagram is from a different era. Here's what a 2026 test layering actually looks like.
The pyramid — wide unit base, narrower integration middle, tiny end-to-end top — was written when integration tests meant "spin up the whole monolith and a real Oracle DB." It's outdated. Modern infrastructure changes the economics: integration tests are cheap, real dependencies are containers, and unit tests with mocks have started to cost us more than they buy.
1. Why the pyramid worked, and why it broke
It worked because real dependencies were slow and flaky, so you minimised them. It broke because mocking a database to test code that depends on a database tests your mock, not your code. The classic failure mode: 95% unit-test coverage, every test green, code does not actually work in production because the mocked Postgres behaved nothing like real Postgres.
2. The shape that actually works in 2026
A diamond, not a pyramid:
- Few — pure unit tests. Reserved for genuinely pure logic: parsers, calculators, formatters. Run in milliseconds.
- Many — integration tests against real dependencies via testcontainers. Real Postgres, real Kafka, real Redis. Run in seconds.
- Few — end-to-end / smoke tests. The "click through the actual app and verify it loads" kind. Run in minutes.
3. Where unit tests still earn their keep
- Domain logic with branching rules ("is this order eligible for refund?").
- Pure transformations: serialisation, parsing, formatting.
- Algorithms: sorting, scheduling, rate limiting math.
If you can write the test without touching a mock, write it as a unit test.
4. Where integration tests dominate
- Anything involving SQL. Especially migrations, transactions, isolation.
- HTTP handlers. The framework's middleware stack is part of the contract.
- Message consumers. Kafka serialisation, retry semantics, DLQ wiring.
- Auth flows. Signing, validation, expiry — all of it touches at least one real piece.
Run them with testcontainers. The startup cost is amortised across the suite; the per-test cost is milliseconds.
5. End-to-end is for the golden paths
Three to five tests, the routes that generate 95% of revenue. They confirm that the system, deployed somewhere shaped like production, works. They will be flaky. Accept that as the price of catching the bugs no other layer catches.
Don't write 200 E2E tests. They will rot. The maintenance cost will dominate and the team will start ignoring failures.
6. The mocking discipline
Mock only what you cannot reach: a third-party payment provider, an external SMS gateway, a flaky upstream you do not control. Mocking a database you ship with is testing the wrong thing.
7. What "fast" means now
- Pure unit suite: under 10 seconds for the whole project.
- Integration suite per service: under 2 minutes.
- E2E suite: under 10 minutes.
If integration is slow, the testcontainers are misconfigured (shared reuse, schema cached, fresh DB per test instead of per suite). Fix that before declaring "we can't do integration tests."
8. Coverage is a lagging metric
Coverage tells you the test ran the line. It does not tell you the test asserted anything useful. A team's quality is measured by escapes to production, not by their CI badge.
The rules of thumb
- If you mock the database, you are testing the mock. Stop.
- If a test is flaky, fix it or delete it. Quarantine is a graveyard.
- Integration tests against real deps catch bugs unit tests never will.
- E2E tests prove deploys, not features.