Integration Testing Scaffolds: a centralized testing approach for microservice architectures
Source: Dev.to
The Blind Spot in Modern Testing
A typical microservice setup already includes:
- Unit tests → solid and fast
- Contract tests → schema compatibility guaranteed
- E2E tests → complete UI workflows covered
So where do failures still happen?
They don’t happen in single services. They happen between services.
Examples
- Data losing precision as it flows through multiple transformations
- OAuth tokens failing in certain service combinations
- Rate‑limiters behaving differently under real load patterns
- Event‑driven flows silently dropping messages
- Transactions that work individually but break when chained
- Cache invalidation not propagating across service boundaries
Unit tests can’t see this. Contract tests can’t see this. And E2E tests? They hit the entire system through the UI, which is too shallow, too slow, and often mocked at the integration points that matter most.
This is where integration API testing should live.
Why a Centralized Testing Repository?
The solution proposed here is a central‑testing‑repository: a single repository dedicated to integration tests that verify cross‑service behavior after deployment.
Benefits
- Provides system‑level confidence now, not after a multi‑year migration completes.
- Offers an organizational owner (e.g., a QA or platform team) for cross‑service failures.
- Documents how systems should interact; a failing test pinpoints the offending service.
Trade‑offs
- Tests are maintained separately from service code.
- Coordination between teams is required.
- Introduces some coupling between services and tests.
Despite these costs, the approach matches the reality of mixed monolith‑microservice landscapes, multiple tech stacks, and distributed ownership.
The Monorepo Insight
A central‑testing‑repository is essentially a lightweight monorepo, but only for integration tests.
- Companies that have fully adopted monorepos (Google, Meta, etc.) get cross‑service testing almost for free: all services live in one place, so writing a test that spans multiple services is just… a test.
- Most companies cannot migrate to a full monorepo due to organizational and tooling costs.
The central‑testing‑repository gives you the testing benefit of a monorepo without requiring a full migration:
- Single repository with visibility into all service APIs.
- Tests run against real deployed services.
- Owns the “glue” that holds the distributed system together.
Yes, this means two PRs when you change an API (one in the service, one in the tests). That moment of coordination is exactly when you catch breaking changes—before they reach production.
Wait, Doesn’t Contract Testing Catch This?
Contract testing and integration testing solve different problems.
| Contract testing | Integration testing | |
|---|---|---|
| Schema matches? | ✅ | ✅ |
| Values correct? | ❌ | ✅ |
| Side effects happen? | ❌ | ✅ |
| Cross‑service consistency? | ❌ | ✅ |
| Timing/ordering correct? | ❌ | ✅ |
- Contract testing verifies structure: Can services talk to each other? Is there a field called
price? Is it a number? Does the response schema match what consumers expect? - Integration testing verifies behavior: Do services work correctly together? Is the value
19.99maintained through multiple transformations? Does the tax calculation match between Cart and Invoice? Did the side effect (inventory reservation, email sent) actually happen?
Real Production Failures That Slip Through
These bugs make it to production because nothing in the test suite is looking for them.
Example 1: Tax Calculation Inconsistency
- Cart Service uses
Decimaltypes: tax = €3.80 - Invoice Service uses
float: tax = €3.79 - For 3 items: Cart shows €11.40, Invoice shows €11.39
Contract test: Both return {tax: number} → Pass.
Integration test: Verifies all services return the same tax amount → Fails.
Example 2: Date Format Interpretation
- Order Service (Java) serializes:
"01/02/2024"(January 2 US format) - Shipping Service (Go) parses: February 1 (EU format)
Contract test: Both handle { "orderDate": "string" } → Pass.
Integration test: Creates order, verifies Shipping Service parsed the correct date → Fails.
Example 3: Status String Casing
- Payment Service (C#) returns:
status: "COMPLETED" - Notification Service (Node.js) expects:
"completed"
Contract test: Both handle { "status": "string" } → Pass.
Integration test: Processes payment, verifies notification triggered → Fails.
In each case, contract tests pass while behavior breaks.
Why E2E Tests Can’t Fill This Gap
When teams lack integration API testing, they try to stuff all backend validation into E2E tests. This leads to several problems:
- Speed: E2E tests are slow, making frequent feedback impractical.
- Flakiness: UI‑driven tests are prone to false negatives due to timing or environment issues.
- Shallow coverage: They often mock the very integration points that need verification.
- Maintenance burden: Changes to UI or unrelated front‑end features cause unrelated test failures.
Consequently, E2E tests become a terrible primary tool for backend integration validation, reinforcing the need for a dedicated, centralized integration testing layer.