Skip to content

PATTERN Cited by 1 source

Header-routed mock vs real dependency

What this is

Header-routed mock vs real dependency is the pattern of using an L7 routing layer (ingress proxy, service mesh, or sidecar) to inspect an inbound request's headers and dispatch the service's outbound call to either the real external dependency or to a mock of that dependency — in the same deployment, the same environment, per-request.

The canonical use is a shared test cluster that serves both load tests (which need mocks for cost + isolation) and other test traffic (which needs real dependencies for integration verification). A statically-wired-to-mocks deployment forces an either-or choice. A header switch lets both test types coexist.

The shape

  1. A load-test tag header is injected into every request by the load generator — typically the same tag used for traffic-source tagging in traces so the two concerns share one identifier.
  2. The tag propagates through the call graph via tracing baggage or explicit mesh-level propagation.
  3. At the ingress / sidecar, a routing predicate on the tag selects either the real-dependency backend or the mock backend.
  4. Apps stay tag-blind. They don't change between test and prod; the switch is transparent.

Zalando instantiation

Source: sources/2021-03-01-zalando-building-an-end-to-end-load-test-automation-system-on-top-of-kubernetes:

"We leveraged header-based routing using Skipper, so a service can decide whether to use mocks or actual dependent service by examining if the request belongs to a load test or not."

The Skipper eskip DSL expresses the switch as a predicate- filtered route pair. One route matches Header("X-Load-Test", "true") and forwards to the Hoverfly mock; the default route forwards to the real external backend.

Skipper is the natural host in Zalando's context because it's already deployed as the ingress across 140+ clusters — adding the predicate-filtered routes is an annotation change, not a new infrastructure component.

Why not separate deployments

Standing up a second, statically-wired-to-mocks deployment of each service sounds simpler and has a good property: no L7 rule surface to maintain. But it has costs:

  • Double the pods for the same service count — at Zalando's "clone production replica count + resource allocation" parity invariant, doubling compute is expensive.
  • Test type cross-contamination. An integration test run against the mocked deployment produces mock behaviour; the test author has to remember which deployment they hit.
  • Load tests can't share resources with QA tests — scheduling gets harder as test types multiply.

Header-routed switching keeps one deployment, one resource allocation, one route-debug-path, with the tag as the single classification axis.

What makes it safe

  • Header tag defaults to "real". Missing header = real dependency. This is fail-safe: a load-test infrastructure bug that drops the header falls through to real dependencies (which is costly but not data-corrupting); a fail-safe in the other direction would call mocks on real user traffic (data corruption).
  • Propagation is hygienic. Tracing libraries' baggage mechanisms + explicit service-mesh propagation lists prevent accidental stripping.
  • Environment scoping. The header-based switching is only configured in test environments. The production environment's route table has no mock backends — so the same header hitting production has no effect.

What can go wrong

  • Tag-strip mid-graph. Any service that drops the header loses the switch; downstream calls go to real dependencies, load-test isolation breaks, third-party side effects fire. This is why mesh-level propagation + explicit tracing- baggage config is required.
  • Mock configured but stateful. State accumulates across test runs if reset isn't explicit. Hoverfly's k/v map needs managing between runs.
  • Real dependency schema changed. The mock's simulation file is stale; the app passes mocked tests but fails against the live dependency. Contract tests complement this pattern; this pattern doesn't subsume them.

Relation to patterns/mock-external-dependencies-for-isolated-load-test

Sibling patterns that compose:

  • mock-external-dependencies-for-isolated-load-testwhy mocks exist (cost + isolation + determinism).
  • header-routed-mock-vs-real-dependencyhow the mocks coexist with real dependencies in a shared test cluster.

Use both together. You could mock without header routing (just deploy a mocked variant of the service), but the pattern combined with header-routing is what makes mocks cheap enough to share a cluster.

Seen in

Last updated · 476 distilled / 1,218 read