Assertions on response codes miss a service that got slower or started calling a downstream it should not. Trace-based tests catch the shape of a request, not just its outcome.

A conventional integration test checks that a checkout request returns a 200 with the right body. It says nothing about whether that request quietly started fanning out to three downstream services instead of one, or whether a step that used to take 40ms now takes 400ms. Trace-based testing asserts on the shape of the request, not only its final outcome.

1. Run the flow and capture its trace

Trigger the checkout flow in a test environment exactly as a normal integration test would, but instead of asserting only on the HTTP response, pull the resulting trace by its ID immediately afterward.

const response = await request(app).post('/checkout').send(cart);
const trace = await helix.getTrace(response.headers['x-trace-id']);

2. Assert on span structure, not just span count

The useful assertions are structural: which services were called, in what order, and whether any span exceeded a duration budget. A span count alone does not catch a call moving from a fast path to a slow one.

expect(trace.spansByService('payments-api')).toHaveLength(1);
expect(trace.spansByService('inventory-service')).toHaveLength(1);
expect(trace.span('pg.query').duration).toBeLessThan(50);
expect(trace.hasCall('checkout-api', 'legacy-pricing-service')).toBe(false);

3. Catch accidental fan-out before it ships

The last assertion above is the one that catches the most expensive class of regression: a refactor that accidentally reintroduces a call to a deprecated service, or an N+1 pattern that turns one downstream call into a dozen. Neither shows up in a response-code assertion, both show up immediately in the span list.

4. Run it in CI against a real, traced environment

This only works if the test environment is genuinely instrumented, not a set of mocks, since the entire point is observing real inter-service behavior. Run trace-based tests as a distinct CI stage against a staging environment with full instrumentation enabled, separate from the faster unit test suite that runs against mocks.

from traces
| where environment == "staging" and test_run_id == "$CI_RUN_ID"
| where service.name == "checkout-api"
| project trace_id, span_count, duration
  • Assert on trace structure, service call graph and per-span duration, not only the HTTP response.
  • Catch accidental fan-out and deprecated downstream calls that a status-code test cannot see.
  • Run trace-based tests against a genuinely instrumented staging environment, not mocks.
  • Keep them as a separate, slower CI stage from the standard unit test suite.