Delivery and ordering
- The same event delivered twice
- Events for one key arriving out of order
- A consumer restarting halfway through a batch
Once a product is split into services, most failures happen between them: a renamed field, a retried event, a timeout nobody set. Agnotic Technologies builds the contract, component, event and resilience tests that catch them before release, in each service team's own pipeline.
Trusted by global innovators
























Software testing for microservices
Microservices testing is checking that independently deployed services still work together: contracts between services, component tests with dependencies stubbed, event and retry handling, and behavior when a dependency fails. Agnotic Technologies builds this testing for the client and places each test in the pipeline of the team that owns the service.
What changes after a split is where failures hide. A monolith fails inside one process you can debug. A set of services fails between them, in a call, a message or a timeout.
This page covers the tests that exist only because the product is split into services. General test layers and framework choice are on our test automation page, and load tests across a chain of services are on the performance testing page.
Test placement table
The placement we agree with each service team. A test that blocks the wrong pipeline either slows every team down or catches nothing.
| Test type | What it proves | Who owns it | Pipeline it blocks |
|---|---|---|---|
| Unit | The service's own rules and calculations are right | The service team | Service build, on every commit |
| Component with stubs | The whole service works through its real API, with its own database real and other services stubbed or virtualized | The service team | Service build, before merge |
| Consumer contract | The calls and messages this service depends on are written down as a contract the provider can check | The consuming team | Consumer build; the contract is published with its version |
| Provider verification | The provider still meets every published consumer contract | The providing team | Provider build; a broken contract stops the merge |
| Messaging and events | Events follow the agreed schema, and consumers handle duplicates, reordering and poison messages | Producer and consumer teams | Both service builds, against a real broker in a container |
| Short-lived environment smoke test | The changed service starts, connects and serves a few key calls beside its real neighbours | The service team, on a shared platform | Release of that service |
| Resilience and fault injection | Timeouts, retries, circuit breakers and fallbacks behave as designed when a dependency is slow or down | Service team with the platform team | Release, on a schedule and before risky changes |
| End-to-end journey | A few journeys that earn money or keep users safe still work across all services | A small cross-team group | Release train or nightly; kept short on purpose |
Most confidence comes from the first five rows, which run without deploying the whole system. End-to-end journeys stay few, because each one depends on every service being up.
Asynchronous messaging
Message-based integration fails in ways a request and response never does. These are the checks we write for each producer and consumer.
Usually not. For most changes we deploy the changed service and its direct dependencies into a short-lived environment, created for the pull request and removed when it closes, and stub or share everything further away.
On Kubernetes this is often one namespace per change, built from the same manifests or Helm charts as production, with seeded test data and an expiry so forgotten environments do not pile up.
A shared staging environment still has a place for release rehearsals. It just stops being the only place a change can be tested, which is what usually leaves teams queuing for their turn.
Fault injection
Each fault runs against a short-lived or staging environment. The expected behavior is written down before the test, so the result is a pass or a fail, not an opinion.
Example: The pricing service takes far longer than usual to answer
Approach: The caller times out at its configured limit, returns a clear error or a fallback, and does not hold threads or connections.
Example: The inventory service returns server errors for a period
Approach: Retries stay within their budget, then the circuit breaker opens and calls fail fast until the dependency recovers.
Example: A service is scaled to zero or its network is blocked
Approach: Features that depend on it degrade as designed, and the rest of the product keeps working.
Example: Producers cannot publish events for several minutes
Approach: Events are kept in an outbox or buffer and published once the broker returns, with none lost or doubled.
Example: A backlog builds after a slow deployment
Approach: Lag shows on dashboards and alerts, and the consumer catches up without breaking ordering per key.
Example: A new release starts failing health checks
Approach: The rollout stops and rolls back, and the contract results show which change caused it.
Diagnosis
When a test that crosses services fails, the first question is where. We wire tests into your tracing so the answer is one click away.
Contract workflow
The order we set up between a consumer team and a provider team, so neither has to deploy the other's service to test a change.
The consumer's tests describe the requests or messages it sends and the parts of each response it reads, and a contract is generated from them.
Contracts go to a shared broker, tagged with the consumer version and the environments that version runs in.
Each provider build replays the published contracts against the real provider code and reports which consumers it satisfies.
Before a service is deployed, the pipeline asks whether this version is compatible with the versions already running in the target environment.
A provider that needs to remove a field adds the new one first, waits until no deployed consumer reads the old one, then removes it.
Failure points
Patterns we find when we review service testing that has grown without a plan.
Challenge
Every change waits for one shared staging environment.
Agnotic approach
Short-lived environments per change, with staging kept for release rehearsals.
Challenge
Hand-written mocks drift away from how the real provider behaves.
Agnotic approach
Consumer-driven contracts, so a stub is only trusted while the provider still passes it.
Challenge
Contract tests check the provider's business rules and break on every change.
Agnotic approach
Contracts cover only the shape and meaning of what the consumer reads; business rules stay in the provider's own tests.
Challenge
Event tests wait with fixed sleeps and fail at random.
Agnotic approach
Tests poll for the expected outcome with a time limit, or read the consumer's output directly.
Challenge
Retries were never tested, so a retried message charged a customer twice.
Agnotic approach
Duplicate delivery tests for every consumer that causes a side effect.
Challenge
A failing end-to-end test takes hours to trace to one service.
Agnotic approach
Trace context in every test, linked from the failure report.
Starting points
Situations that bring engineering leaders to us, and where we usually start.
The old end-to-end suite still runs against everything and nobody trusts it. We start with contracts at the busiest service boundaries.
Teams queue for one environment. We start with component tests and short-lived environments for the services that change most.
A provider change broke a consumer in production. We start with consumer contracts for that provider and a deployment check.
A retried message sent two emails or two payments. We start with idempotency and duplicate delivery tests on those consumers.
A handful of services and one team. We keep it small: component tests, a few contracts and one short end-to-end journey.
The platform changes under every service. We add resilience tests and per-change environments as part of the move.
Scope
We do not publish prices. Microservices testing cost depends on these drivers, which we size on the scoping call.
How many services, and how many consumer and provider pairs need contracts.
Which brokers and event flows are involved, and how many consumers cause side effects.
Whether your platform can already create an environment per change, or that has to be built first.
Which unit, component and end-to-end tests exist, and which ones the teams trust.
Whether services already emit traces, or instrumentation comes first.
How many service pipelines need new gates, and which team owns each one.
Short facts from the projects and standards behind this testing approach, each linked to its primary source.
Pact describes contract testing as checking each application in isolation to confirm the messages it sends or receives match a shared, documented contract, and Pact generates that contract while the consumer's automated tests run.
Source: Pact documentation: Introduction (Pact Foundation)NIST SP 800-204 lists the core features a microservices system needs, including service discovery, security monitoring, availability and resiliency techniques such as circuit breakers, and load balancing and throttling.
Source: NIST SP 800-204, Security Strategies for Microservices-based Application SystemsIn OpenTelemetry, a trace is the path of a request through an application, and a producer span marks a job that may be processed later; the parent of a consumer span is always a producer span.
Source: OpenTelemetry documentation: Traces (CNCF project)Kubernetes namespaces isolate groups of resources within a single cluster, and resource names only need to be unique within a namespace, not across namespaces.
Source: Kubernetes documentation: NamespacesReferences
The specifications and guidance behind the contracts, traces and environments on this page.