Contract Testing vs Integration Testing: When to Use Each
Contract testing and integration testing both verify that services work together correctly. They solve different problems, run at different points in the development cycle, and provide different types of confidence. Understanding when to use each — and how to use them together — is the key to effective microservice testing.
What Each Test Type Does
Integration testing runs two or more real services simultaneously and verifies their interactions through actual HTTP calls, database writes, and message passing. You deploy the real user service and the real order service together, make a real request, and check the real response.
Contract testing runs each service independently, without the other. A consumer test mocks the provider and records the expected interaction as a contract. A provider test replays that interaction against the real provider. Neither service needs to run simultaneously with the other during testing.
The Problem Integration Testing Solves
Integration tests answer: "Do these two services work together correctly right now?"
The "right now" matters. Integration tests catch:
- Business logic errors: The provider returns the right shape but wrong values
- Data consistency: Transactions that span services behave correctly
- Infrastructure issues: Network timeouts, retry behavior, circuit breaker logic
- Real database behavior: Query performance, transaction isolation levels, constraint violations
- Authentication flow: OAuth tokens, API keys, service-to-service auth headers
These are things contract tests can't check. A contract test verifies the API shape matches expectations, but a provider could return status: "active" when "suspended" is correct and the contract test would still pass.
The Problem Contract Testing Solves
Contract tests answer: "Will my change break a consumer?"
This is the question integration tests answer too slowly and too expensively. Consider:
- Order service calls user service
- User service team renames
emailtoemailAddress - Integration tests would catch this — but only if they run against both services simultaneously
In a microservice organization, running integration tests across service boundaries requires:
- Coordinating deployment of both services to a shared test environment
- Waiting for both services to be in a deployable state
- Dealing with network latency, environment stability, and inter-service dependencies
- Figuring out which service caused a failure when tests break
Contract tests solve this without the coordination overhead. User service tests run in isolation. If the user service team renames email, their provider verification fails immediately — before they deploy, before the integration environment, before any downstream service is affected.
Side-by-Side Comparison
| Integration Tests | Contract Tests | |
|---|---|---|
| Services required | Multiple (running simultaneously) | One (consumer OR provider, separately) |
| Catches | Logic errors, data issues, infrastructure behavior | API shape mismatches between consumer and provider |
| Speed | Slow (seconds to minutes per test) | Fast (milliseconds) |
| Reliability | Flaky (network, environment, timing) | Reliable (deterministic, no network calls) |
| Coordination | High (all services must be available) | None (each service tests independently) |
| When they run | After deployment to staging | In unit test phase, before deployment |
| Feedback delay | Hours (wait for staging deployment) | Minutes (during PR) |
| What they miss | API shape drift over time | Business logic, data errors |
When Integration Tests Are the Right Choice
Single-process or small architectures: If you have 2-3 services that deploy together, integration testing is simpler than setting up contract testing infrastructure.
Testing business logic across service boundaries: If order creation requires user validation and payment processing, and the correctness depends on what all three services do together, integration tests are necessary.
Testing infrastructure behavior: Retry logic, circuit breakers, timeout handling, and connection pooling can only be tested realistically with real services.
Testing third-party integrations: You don't control Stripe's or Twilio's API. You can write consumer contract tests against their specs, but real integration tests tell you whether your implementation actually works.
Compliance and acceptance testing: Regulators and enterprise customers often require evidence of end-to-end testing, not just component-level verification.
When Contract Tests Are the Right Choice
Preventing breaking API changes: The primary use case. If consumer teams should know immediately when a provider change breaks their expectations, consumer-driven contract tests are the right mechanism.
Fast feedback on API compatibility: Running integration tests takes 10-30 minutes minimum (deploy, test, report). Contract tests run in the unit test phase and complete in seconds.
Polyglot microservices: 8 services in 5 languages with a shared API contract. Integration testing all combinations is combinatorially expensive. Contract testing scales.
Testing at PR time, not staging time: Contract tests can run on every pull request because they don't require a running environment. Integration tests on every PR require a staging environment per PR (expensive) or a shared staging environment (flaky).
Reducing integration test flakiness: If your integration test suite is flaky (random failures due to timing, network, or environment instability), contract tests provide stable, reliable API compatibility verification.
The Trap: Replacing Integration Tests with Contract Tests
Teams new to contract testing sometimes try to replace integration tests entirely. This is a mistake.
What you'd lose:
- Verification that business logic is correct across service boundaries
- Evidence that data flows correctly (not just that API shapes match)
- Confidence about real infrastructure behavior (timeout handling, retry logic)
- Testing of stateful workflows that span services
What remains necessary:
Contract tests tell you the user service API returns an object with id, name, and email fields. Integration tests tell you that the order service correctly creates an order using those user fields, stores it in the database, and returns the right order ID.
Both questions matter. Contract tests answer the first faster and more reliably. Integration tests answer the second.
Combining Both Effectively
Phase 1 — Unit + Contract tests (fast path, every PR):
Unit tests — 2 minutes
Contract tests — 1 minute
├── Consumer tests (generate pact files)
└── Provider verificationThis catches: internal logic errors, API compatibility problems.
Phase 2 — Integration tests (slower path, before merging to main):
Integration tests — 10-15 minutes
└── Real services running together
├── Happy path flows
├── Error handling
└── Infrastructure behaviorThis catches: cross-service logic errors, data consistency issues.
Phase 3 — E2E tests (slowest path, against staging):
E2E tests — 20-30 minutes
└── Full user flows against deployed appThis catches: environment-specific issues, full workflow correctness.
The contract testing layer at Phase 1 reduces what needs to be tested at Phase 2. If contract tests catch all API compatibility issues early, integration tests can focus on business logic and behavior instead of also checking "does the response have the right fields?"
Practical Decision Framework
Ask these questions to decide which type of test to write:
"Will this test require two services to run simultaneously?"
- Yes, and that's the only way to test it → Integration test
- No, or yes but it doesn't need to be → Contract test candidate
"Does this test verify that the API shape stays stable?"
- Yes → Contract test
- No, it verifies behavior → Integration or unit test
"Can this test run on every pull request?"
- Yes → Unit or contract test
- No (requires staging, shared environment) → Integration or E2E test
"Am I testing a third-party API?"
- Yes → Integration test (the API doesn't change based on your contract)
- No → Contract test candidate
Example: Choosing for a Payment Flow
order-service → payment-service → stripe (external)For order-service ↔ payment-service:
- Contract test: Verify payment-service API shape doesn't break order-service
- Integration test: Verify the full payment initiation flow works correctly with real data
For payment-service ↔ stripe:
- Integration test only: You can't publish contracts to Stripe
- PactFlow's bidirectional contracts or WireMock stubs can mock Stripe for consumer tests
For the full flow (order → payment → stripe):
- E2E test in test mode: Stripe test mode simulates real payment flows without real charges
Summary
Use contract testing when:
- API compatibility between teams needs continuous verification
- You need fast feedback (PR-time, not staging-time)
- Services are polyglot or maintained by different teams
- Integration test flakiness is a problem
Use integration testing when:
- Business logic correctness requires multiple services running together
- You're testing infrastructure behavior (timeouts, retries, circuit breakers)
- You need to verify third-party integrations
- Compliance requires evidence of end-to-end testing
Use both in every serious microservice architecture. Contract tests provide fast, reliable API compatibility checking. Integration tests provide confidence that the services do the right thing when they interact. Together, they cover what neither handles alone.