If your architecture has more than 3 services, your E2E tests are probably a pain point. They're slow, flaky, and require every service to be running. Contract testing solves a specific part of this problem, but it's not a replacement for E2E. Here's how to use both.
The problem with E2E across microservices
In a monolith, E2E testing is straightforward: start the app, run the tests. In a microservices architecture, you need to spin up 5-15 services, their databases, message queues, and external dependencies. Tests become:
- Slow: 30-60 minute CI runs waiting for services to start
- Flaky: Any service being slow or unhealthy fails the entire suite
- Expensive: CI infrastructure costs scale with the number of services
- Hard to debug: A failure could be in any of 10 services
What contract testing actually does
Contract testing (Pact, PactFlow, or similar) verifies that two services agree on the API contract between them. The consumer defines what it expects. The provider verifies it can fulfill those expectations. No network calls, no running services, no shared environments.
// Consumer test (frontend expects this from /api/users)
const interaction = {
state: 'user exists',
uponReceiving: 'a request for user profile',
withRequest: {
method: 'GET',
path: '/api/users/123',
},
willRespondWith: {
status: 200,
body: {
id: 123,
name: like('Art Shllaku'),
email: like('contact@rivora.tech'),
},
},
};When to use contract testing
- API boundaries between services: Verify request/response schemas without running the full stack
- Breaking change detection: Catch incompatible API changes before they reach staging
- Independent deployability: Each service can deploy confidently knowing its contracts are met
- Fast feedback: Contract tests run in seconds, not minutes
When to keep E2E testing
- Critical user journeys: Login, purchase, onboarding. These need real browser testing.
- Cross-service workflows: Flows that touch 3+ services and involve real data transformation
- Third-party integrations: Payment providers, auth systems, external APIs
- Visual/UX validation: Layout, responsiveness, animations
The right combination
For most teams with microservices, the strategy looks like this:
- Contract tests: Every API boundary between services. Run on every PR. Sub-minute execution.
- Integration tests: Each service's API tested against its own database. Run on every PR.
- E2E tests: 10-20 critical user journeys. Run on merge to main or on a schedule. Under 15 minutes.
This gives you fast feedback on contract compliance (seconds), service-level correctness (minutes), and user-level validation (minutes). The total coverage is better than 500 E2E tests, and the suite runs 10x faster.
Getting started
If you're drowning in slow E2E tests across microservices, start by identifying your most-tested API boundaries. Add contract tests for those first. Then remove the E2E tests that were only verifying those same contracts. You'll see an immediate improvement in CI speed and stability.
Need help designing a testing strategy for your microservices? Book a call. We help teams find the right balance between speed and coverage.