A test that only checks the response body will happily pass while your service quietly starts making three database calls where it used to make one. The output is right; the path is wrong. Traces see what assertions miss.
Assertions on shape, not just output
Trace-based testing runs your normal test, then asserts on the trace it produced. Did the request touch the cache? Did it fan out to more services than expected? Did a span appear that should not be on this path? These are correctness questions your response assertions cannot ask.
What it catches
- N+1 regressions — a loop that quietly started issuing one query per item.
- Accidental dependencies — a refactor that made the login path call the billing service.
- Silent retries — a change that turned one attempt into three, hidden behind a still-correct result.
Assert on the trace in CI
trace_of(test "checkout succeeds")
| assert count(span where db.system == "postgres") <= 2
| assert not exists(span where service == "billing")
| assert max(duration) < 500ms
Where it fits
Trace-based tests are not a replacement for unit tests; they are a complement that guards architecture. We reserve them for the handful of critical paths where "how" matters as much as "what" — checkout, auth, and anything that touches money.