The Computing Series

The Concept

The test pyramid defines the proportion and purpose of each test layer.

Unit tests are the base of the pyramid: many tests, fast execution, testing one unit of behaviour in isolation. “Isolation” means dependencies are replaced with test doubles (stubs, fakes, mocks). A unit test for calculateDiscount provides an Order object and a DiscountCode and asserts on the returned Money — no database, no network. Unit tests are fast (milliseconds each), reliable (no external state), and targeted (a failure points to a specific function). They cover pure logic and business rules.

Integration tests test the behaviour of two or more components together, or the behaviour of one component against real infrastructure (a database, a message queue). An integration test for PostgresOrderRepository.save creates a test database, calls save, queries the database directly, and asserts on the stored values. Slower than unit tests (100ms to 1s each), but they catch the bugs that unit tests cannot: ORM mapping errors, SQL constraint violations, transaction isolation issues.

Contract tests verify that a service’s published interface matches what consumers expect. Consumer-driven contract tests are defined by the consumer: “I expect the orders service to respond with {id, status, items, total} when I call GET /orders/:id.” The orders service runs these tests as part of its test suite, verifying that it does not break its consumers without knowing who they are. Contract tests catch FM8 (Schema/Contract Violation) before deployment, not after.

End-to-end tests exercise the full system through its user-facing interface. They catch failures that only appear when all components are running together: configuration mismatches, environment-specific bugs, workflow failures. They are slow (seconds to minutes each), flaky (network, timing, external service state), and expensive to maintain. Their value is high for critical user journeys; their cost makes them unsuitable as the primary test layer.

The test pyramid implies: write many unit tests, some integration tests, a few contract tests, very few end-to-end tests. The test iceberg (inverted pyramid) — more end-to-end tests than unit tests — produces the situation described in the introduction: slow, flaky tests that developers avoid.

When to use each layer: - Unit tests: all pure functions, all business rules, all edge cases and error conditions - Integration tests: all database operations, all queue interactions, all external API clients - Contract tests: all service-to-service interfaces, all public API endpoints - End-to-end tests: critical user journeys (sign up, purchase, payment), smoke tests for deployment verification

Read in the book →