Integration, contract and end-to-end tests
Tests wider than a unit: integration, consumer-driven contract, service component and end-to-end, plus testing in production.
key idea / coordinatorservice / good pathin progress / watch outproblem / failureneutral
Integration tests
Test the adapters, not whole services: one adapter at a time, and contracts between services.
| Interaction | FTGO example | Contract holds |
|---|---|---|
| REST request/response | API gateway → Order svc | HTTP request + response |
| Publish/subscribe | Order svc → Order History | One event |
| Async request/response | Order svc → Kitchen svc | Command + reply message |
Integration tests with a real database
Bootstrapping Microservices tests a whole microservice through its real HTTP interface, with no mocks. The database is real too, and it is loaded with known test data called a fixture.
Integration tests are slower than unit tests because a real server and database start, but they find problems that hide between modules, and one test covers a lot of code. Docker Compose gives every developer the same database.
Contract tests for REST
One contract becomes a provider test and a consumer stub.
Contract tests for messaging
The same idea works for events and for commands with replies.
Consumer-driven contracts and Pact
A schema catches structural breakage, such as a missing field. It cannot catch semantic breakage, where behavior changes but the shape does not. End-to-end tests catch both but at high cost. Consumer-driven contracts (CDCs) catch semantic breakage with a small scope. From Building Microservices, ch. 9.
| Check | Catches | Scope |
|---|---|---|
| Schema | Structural breaks (field removed) | Tiny |
| Consumer-driven contract | Semantic breaks (behavior changed) | One service, like a service test |
| End-to-end test | Both, plus wiring problems | Many services, slow |
The Pact Broker also records when a contract was last verified and which services depend on which. Spring Cloud Contract is an alternative, but it only suits an all-JVM stack. Good practice: people from both teams write the contracts together. A CDC is a record of a conversation, and a broken contract is the cue for the next one.
Contract tests and test categories
End-to-end tests cover the whole system but are slow, hard to keep stable and need a full copy of production. Contracts check the seams cheaply. From Practical Event-Driven Microservices Architecture, ch. 10.
Component tests
Acceptance tests for one whole service, treated as a black box, with its neighbors stubbed.
Which tests does an event-driven service need?
Pick tests by what they prove and how much they cost. Each layer of the service gets unit tests, and the service as a whole gets a few broader ones. From Practical Event-Driven Microservices Architecture, ch. 10.
| Test | Scope | Broker and database |
|---|---|---|
| Unit | One class or function in a layer. | None |
| Component | The whole service, in memory. | Fakes or in-memory stand-ins |
| Half-breed integration | The service with real infrastructure. | Real broker and database, other services faked |
| Contract | The event schema between two services. | None needed |
End-to-end tests
Test the whole application, but only a few key journeys.
Browser tests with Playwright
Bootstrapping Microservices boots the application with Docker Compose and drives its UI with Playwright. The test sees the app the way a customer does. From Bootstrapping Microservices, ch. 9.
A test loads a fixture, opens a page, then asserts what it shows, for example that the video list has two entries. Playwright runs headless, can try several browsers, and gives good reports, so it fits a CI pipeline via npm test. It can also mock the backend to test a frontend alone, though that is then not truly end-to-end.
Why end-to-end tests hurt
One service changes, and you want to know it did not break the helpdesk or web shop. The usual answer is a shared end-to-end stage that every pipeline feeds into. From Building Microservices.
| Pitfall | Why it hurts |
|---|---|
| Flaky and brittle | More moving parts mean failures that say nothing about the feature. People rerun and stop trusting the suite. |
| Slow | Suites of a day, or six weeks for a regression run. Failures surface after you have moved on. |
| Great pile-up | A broken stage blocks check-ins, changes accumulate and each release gets riskier. The fix is a faster suite. |
| Metaversion | "These versions work together" slides into one version number and deploying everything at once. |
| No independent testing | Shared test environments and shared suites force teams to coordinate. High performers test on demand. |
Newman is convinced that at a certain organizational scale you need to move away from cross-team end-to-end tests.
Shrinking the end-to-end suite
Think of end-to-end tests as training wheels. While you learn contracts and production monitoring they trade speed for safety. As the other safeguards improve, you need fewer of them. From Building Microservices.
| Question | Best tool |
|---|---|
| Will my change break a consumer? | Consumer-driven contract |
| Does the service work with neighbors stubbed? | Service test |
| Does one key user journey work? | A few end-to-end tests |
| Is it working for real users right now? | Testing in production (next) |
Testing in production
Pre-production tests cannot drive the chance of failure to zero. A distributed system has too many surprises, so testing where the software really runs gives better feedback. Much of it is safe, and you may already do some. From Building Microservices, ch. 9 and 10.
| Technique | What happens | Real users affected? |
|---|---|---|
| Ping / health check | Is the instance alive? | No |
| Smoke test | Automated check of a new deployment, before release | No |
| Synthetic transaction | Fake user with known input and expected output, run regularly | No (fake data) |
| Canary release | New version for a small slice, widen if healthy | A few |
| A/B test | Two versions side by side, see which performs better | Split |
| Parallel run | Request goes to old and new; user sees one, you compare | No |
| Chaos engineering | Inject faults, e.g. Chaos Monkey stops VMs | System under stress |
Testing event-driven services in production
Pre-production environments never match live traffic. Safe production techniques complement them. From Practical Event-Driven Microservices Architecture, ch. 10.
| Technique | What it proves | Risk to manage |
|---|---|---|
| Shadowing | New code gives sane results on real events. | Duplicate side effects, extra load |
| Canary | Real users are fine on the new version. | A small share sees the bug first |
| Feature flags | Code can ship dark and be enabled slowly. | Old flags pile up; remove them |
| Automated production tests | Key flows work now, using test accounts. | Test data must stay out of reports |
Performance and robustness tests
Newman prefers cross-functional requirements to the name non-functional: they emerge from work across the system. Many can only be fully met in production, but tests show whether you are heading the right way. From Building Microservices.
Splitting a system adds network calls. One database call may become several service calls, and one slow link in a synchronous chain slows everything. So test performance early, not just before launch.
Without a target from your SLOs, fail the build when results change too much from the previous one. Robustness tests re-create failures, for example an artificial timeout between a service and its stub, to check that retries or circuit breakers work. They are worth it for shared code such as a service mesh default.
Recap
- Integration tests check adapters: persistence, REST and messaging.
- Contracts are example messages used to test both consumer and provider.
- Component tests run one service as a black box with stubbed dependencies.
- Keep end-to-end tests to a few user journeys.
- Integration tests run a service through its real API against a real database with fixtures (Jest, Docker Compose). Playwright drives the UI of an app booted by Compose.
- Consumer-driven contracts (for example Pact) catch semantic breaks in one service at service-test cost. Schemas catch only structural breaks.
- End-to-end tests are flaky, slow and hurt independent deployment. Shrink them as contracts and production checks mature.
- Test in production: smoke tests, synthetic transactions, canaries, A/B and parallel runs. Balance fewer failures (MTBF) with faster recovery (MTTR).
- Test each service with unit, component, half-breed integration and contract tests. Consumer-driven contracts protect the seams.
- Build end-to-end quality from many small checks, then add shadowing, canaries, feature flags and production tests.
Quick check
Show answer
It generates a provider-side test that calls OrderController, and it configures a WireMock stub that the API gateway's proxy test calls instead of the real Order Service.
Show answer
A consumer-driven contract. Schemas only check structure. A contract holds a sample request and the expected response, and the producer runs it on every build.
Show answer
A producer change that breaks what a consumer relies on, such as a renamed field. The producer build fails before the release.
Show answer
Run it as a shadow in its own consumer group, with side effects disabled, and compare its output with the live version.