You're starting a brand-new microservice — design its testing strategy from scratch
The flagship, closing question of this course: unit, contract, integration, end-to-end, load, and testing-in-production, tied together into one pipeline from first commit to production.
LEARN BY DOINGPut your intuition to work3 steps
You're starting a brand-new microservice — design its testing strategy from scratch
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#The Last Whiteboard Question: Build the Whole Strategy
This is the question that separates "I know how to write a test" from "I know how to make a team confident enough to ship on a Friday." There's no single correct diagram, but there is a coherent way to reason about it out loud: start from what a single commit can prove cheaply, and work outward to what only real traffic, over time, can prove at all.
#Layer by Layer
Unit tests sit at the base: pure business logic, isolated from the network, the database, and the clock, with collaborators replaced by fakes or mocks. They run in seconds, so they run on every keystroke a developer cares to trigger, and on every commit in CI. Their job is narrow on purpose — catch a logic bug in the discount calculation, not a wiring bug in how the service talks to Postgres.
Contract tests sit next: for every boundary this service has with another team's service — as a consumer of an API it calls, or as a provider of an API it exposes — a consumer-driven contract (Pact is the common tool) captures the exact shape of requests and responses both sides agree to. The consumer's contract test runs against a mock provider built from that contract; the provider verifies the same contract against its real implementation, both independently, in each team's own CI, with no shared environment and no flakiness from two services being deployed at once. This is what lets a hundred microservices avoid needing a hundred-service staging environment just to catch a renamed field.
Integration tests come next: this is where the service's own boundaries — its actual SQL, its actual message consumer, its actual cache client — run against real dependencies via Testcontainers (a real Postgres, a real Kafka broker, a real Redis), inside CI, hermetically, torn down after the run. This is the layer that catches "the migration didn't add the index the query needs" or "the consumer doesn't handle a poison message," neither of which a mock would ever reveal.
End-to-end tests are deliberately few: a handful of critical user journeys — place an order, complete checkout, authenticate — exercised as a black box against a fully deployed staging environment, through the real HTTP or gRPC boundary, with nothing internal mocked. They're slow and comparatively brittle, so the strategy keeps their count small and their scope focused on the paths that would be a headline if they broke.
Load and performance tests validate a different axis entirely: not "is the logic correct" but "does the system meet its latency and throughput SLOs at expected and peak load, and does it degrade gracefully past that." These are expensive to run, so they don't run on every commit — typically nightly, or gating a release branch, using realistic traffic shapes and data volumes.
Testing in production is the layer that never turns off: canary analysis comparing a new version's golden signals against the baseline at a small traffic percentage, feature flags controlling exposure and giving an instant kill switch, synthetic monitoring hitting production endpoints on a schedule to catch issues between real user requests, and SLO-based alerting as the ultimate regression detector — the one check that's still running six months after the code shipped, when nobody's looking at it anymore.
#What Runs Where
| Layer | Catches | Speed | Runs |
|---|---|---|---|
| Unit | Logic bugs in isolated code | Seconds | Every commit, pre-merge |
| Contract | Breaking API changes between services | Seconds | Every commit, pre-merge, independently per team |
| Integration (Testcontainers) | Real SQL/broker/cache wiring bugs | Tens of seconds to a few minutes | Every commit, pre-merge |
| End-to-end | Critical journeys broken across the whole stack | Minutes | Post-merge, against staging, before promotion |
| Load / performance | SLO violations under realistic load | Tens of minutes | Nightly or release-gated, not per commit |
| Testing in production | Real-traffic regressions, capacity surprises, third-party drift | Continuous | Always, post-deploy, forever |
#Tying It Together
#Ownership and Test Data
None of this works if it's one team's job. The developers who own the service own its unit, contract, and integration tests — those live in the same repo and ship in the same pull request as the feature. Whoever owns the release process partners on end-to-end and load testing, because those need shared environments and realistic data. And production signals — SLOs, canary thresholds, alert routing — are everyone's job, because they're the only layer that's still watching after the sprint ends.
Test data deserves its own sentence: unit tests build data in memory per test; integration tests seed a fresh Testcontainers instance per run so there's no shared, decaying fixture database to keep synchronized; end-to-end and load tests need a maintained, production-shaped dataset in staging, which is usually the highest-maintenance, most-neglected part of the whole strategy. Budget real time for it, because a stale staging dataset quietly turns "we ran the e2e suite" into theater.
#The Shape, Not Just the List
Drawn as a pyramid, this is: many unit and contract tests at the base, a meaningful but smaller layer of integration tests, a thin layer of end-to-end tests, and load testing plus testing-in-production as an ongoing band across the top rather than a one-time gate — because unlike every layer below it, production observability never stops running.
#Closing
If you've worked through this course from the first lesson to this one, you've now covered the same ground senior engineers spend years accumulating on the job: how a request actually moves through a system, where it breaks, how to reason about consistency and failure, and — in this last module — how to build the confidence that lets a team ship without holding its breath. Testing strategy is a fitting place to end, because it's the discipline that ties every other topic in this course back together: an API design only matters if it's tested at the right boundary, a database choice only pays off if its query behavior is verified against the real engine, a distributed system's resilience is only real once it's been proven under a fault you injected on purpose instead of one that finds you at 3 a.m. Carry the habit forward more than any single answer: whatever system you're asked about next, in an interview or in a postmortem, ask the same question this course kept asking — what would actually catch this, how cheaply, and how soon. Good luck out there.
🧠 Check your understanding
0/2 · 0/2 answered1. In a healthy testing strategy for a new microservice, why do contract tests matter even though integration tests already exercise real dependencies?
2. Why does 'testing in production' (canary analysis, synthetic monitoring, SLO alerting) belong in the strategy even after end-to-end and load tests already passed?