Test Strategy for Microservices

8 min read

"Write tests at every layer" is not a strategy — it is a statement of intent with no actionable guidance. Strategy is how you answer a more specific question: given our particular system — its number of services, communication patterns, rate of change, business-critical operations, and team structure — what tests do we write, at what layer, and who owns them? The answer is different for a 5-service system with a single team than it is for a 30-service system with a team per service. This lesson gives you the questions to ask and the tradeoffs each answer implies.

The five questions that shape strategy

1. How many services and how tightly coupled are they?

A system with 5 services and 8 inter-service API calls has relatively few integration boundaries to cover. A system with 30 services and 90 inter-service calls has an enormous surface area of potential contract mismatches. More services means more dependency edges means more contract tests. If your service count is growing, your contract test investment needs to grow proportionally — or versioning incidents will grow proportionally instead.

2. Is communication predominantly synchronous or asynchronous?

A system that calls services directly over HTTP can detect a broken API contract at the network call. A system that uses Kafka or RabbitMQ for most inter-service communication can have a broken message schema for days before anyone notices, because the consumer simply stops processing without an obvious error. Async-heavy systems need investment in event testing patterns: publishing test events and asserting on consumer output, and contract tests on message schemas, not just HTTP APIs.

3. How often do the APIs change?

A team shipping API changes multiple times per week needs contract tests running in CI as a deployment gate — the coupling between change velocity and integration risk is too high to manage manually. A team with a stable, versioned API that changes twice a year can afford to run contract tests less frequently and rely more on explicit versioning conventions. The investment in contract testing scales with the rate of change, not the size of the system.

4. Which operations are business-critical?

Not all service interactions carry equal business weight. A user changing their profile photo failing silently is an annoyance. A payment charge succeeding but an order not being created is a financial and trust incident. Authentication failing means no user can access anything. Business-critical operations — payments, authentication, inventory reservation, order creation — deserve deeper integration test coverage, explicit failure-mode testing, and dedicated contract tests. Peripheral operations can be covered more lightly.

5. What is the team structure?

Conway's Law predicts that your test architecture will mirror your team architecture whether you plan it or not. If Team A owns the Order Service and Team B owns the Payment Service, the integration tests at their boundary will fall into a gap unless ownership is explicitly assigned. Contract tests sit exactly at this boundary — they are co-owned artifacts between a consumer team and a provider team. Designing test ownership up front, aligned to service ownership, prevents coverage gaps from forming at team boundaries.

Practical allocation for a 10-service system

These numbers are starting points, not targets. Calibrate to your specific risk and change profile.

Per-service (owned by the team that owns the service):

  • Unit tests: roughly 200 per service — pure logic, no I/O, fast feedback. Functions, algorithms, transformations, validation rules.
  • Component tests: 20-30 per service — the service running in isolation with its own database (real, not mocked) and all outbound dependencies replaced with WireMock stubs. These verify the service's own behaviour end-to-end without touching other services.
  • Integration tests: 5-10 per service — the service running against one real downstream dependency at a time. Covers the most important inter-service calls with a real responder rather than a stub.

Cross-service (co-owned at the boundary):

  • Pact contract tests: 10-20 tests per consumer-provider pair. The consumer publishes a contract; the provider verifies it in CI. Every inter-service HTTP call and every event schema should have at least one contract test covering the critical fields.

System-wide (owned by central QA or a shared platform team):

  • E2E tests: 5-10 tests covering the absolute critical paths. Checkout. Login. The core value delivery that the entire system exists to provide. These tests are expensive to maintain and slow to run. They catch integration and environment problems that lower layers miss. Keep the count small and the coverage meaningful.
Test Strategy
  • – Unit tests
  • – Component tests
  • – Team ownership
  • – Pact consumer-driven
  • – Boundary ownership
  • – CI pipeline gate
  • – Service pairs
  • – Docker Compose
  • – Per-PR environments
  • 5-10 critical paths –
  • Central ownership –
  • Shared staging –

Ownership models

Team-per-service (microservices ideal): Each team owns unit tests, component tests, and integration tests for their service. Contract tests are co-owned — the consumer team writes the expectations, the provider team verifies them. There is no central QA team. E2E tests either don't exist or are owned by a platform team. This model works well when teams are mature and comfortable owning quality end-to-end. The risk is that E2E and cross-cutting concerns get no investment.

Central QA team: A central QA team owns the integration tests and E2E tests. Service teams own their unit tests. This is the legacy model from monolith testing carried into microservices. The central team has distance from the code and becomes a bottleneck — they can't keep up with the deployment velocity of 10 independent service teams. Coverage at the integration layer lags behind development. This model rarely scales past 5-6 services.

Hybrid (most common in practice): Service teams own unit tests and component tests. Contract tests are co-owned. A central QA or platform team owns the E2E suite and shared test infrastructure — Docker Compose configurations, test data management tooling, shared WireMock setup. This distributes ownership where it is most effective while giving the cross-cutting concerns a home. Most teams that have been running microservices for more than two years land here.

Test environment strategy

The test environment strategy is the operational side of the test strategy. The environments you provision determine what tests you can run and when.

Developer local: Docker Compose per developer. Each developer runs the subset of services relevant to their current task. The full 10-service stack is available but not required for day-to-day development. Unit and component tests run here on every save.

Per-PR ephemeral environment: When a pull request opens, CI provisions a fresh environment — all services, real databases, no shared state from previous test runs. Integration tests and contract verifications run here. The environment tears down when the PR closes. This is the most important environment for catching integration problems before merge.

Shared staging: A single persistent environment kept as close to production configuration as possible. This is where exploratory testing happens, where QA engineers reproduce reported bugs, and where the E2E suite runs on a schedule. Shared state can accumulate here — it needs periodic resets or careful test data management.

Performance environment: Separate from functional testing entirely. Production-like data volumes, production-like infrastructure sizing, and no functional tests running concurrently to contaminate the timing results. Load tests and stress tests run here on a cadence separate from the functional test cadence.

The principle: match environment complexity and cost to the test layer that needs it. E2E tests are the only layer that needs every service running simultaneously. Everything else can run in a smaller, cheaper environment.

Cross-reference

The test ownership and test pyramid principles that apply within a single service — how to balance unit, integration, and E2E coverage, and who decides which layer owns which scenario — are covered in the Test Automation Frameworks course under layered architecture and separation of concerns. The patterns in this lesson extend those principles to the multi-service context.

⚠️ Common mistakes

  • Writing the strategy without answering the five questions. A strategy copied from a conference talk or a blog post may describe a system very different from yours. The numbers and patterns that work for Netflix's 500-service deployment are not the starting point for a 6-service B2B SaaS product. Answer the five questions for your specific system before deciding anything.
  • Assigning E2E test ownership to a central team and calling it done. A central team owning E2E tests tends to produce a suite that grows slowly and breaks often — the team is too distant from the day-to-day service changes to keep the suite green. E2E tests need a service team sponsor who flags when a change will affect the E2E scenarios.
  • No contract tests between async producers and consumers. Teams that add Pact for HTTP APIs and forget about Kafka event schemas have covered half the integration surface. A renamed event field causes the same silent failure as a renamed JSON field in an HTTP response. All inter-service contracts — HTTP and event-based — need coverage.
  • Treating the staging environment as the only integration environment. When staging is the only place integration tests run, every merge to main creates a queue for the shared environment. Per-PR ephemeral environments move integration feedback to the point where the code is written, not the point where it's already merged.

🎯 Practice task

Draft a test strategy for a hypothetical system — 40 minutes.

  1. Define your system. Sketch a 6-service e-commerce system: User Service, Product Service, Order Service, Payment Service, Notification Service, and an API Gateway. Draw the inter-service calls — which services call which, and whether each call is synchronous (HTTP) or asynchronous (event).
  2. Answer the five questions. Work through each question for this system: How many boundaries? Is it sync or async dominant? Which APIs change most frequently? Which paths are business-critical? What team structure would make sense?
  3. Produce a test count table. For each service, estimate the unit, component, and integration test counts you would target. For each consumer-provider pair with a contract boundary, note how many Pact tests you'd write. For the system-wide E2E suite, list the 5 critical scenarios.
  4. Assign ownership. Decide whether you're using team-per-service, central QA, or hybrid. For each test category in your table, name which team owns it. Identify any gaps — categories that no team is responsible for.
  5. Identify your riskiest gap. Looking at your ownership table and your five-question answers, what is the single most likely place a bug could escape to production undetected? What one test addition would reduce that risk the most? Write a one-paragraph justification.

Next lesson: component testing in detail — how to run a single service in isolation with WireMock stubs for its dependencies and a real database, and how to structure component tests for maximum coverage with minimum environment complexity.

// tip to track lessons you complete and pick up where you left off across devices.