- A test session is created (automatically, by the fixture or extension).
- Stages are inserted — CEL expressions bound to specific VPoints.
- Sync — the test waits until connected services confirm they received the stages.
- Trigger — the test calls the service, passing the session ID.
- Assert — on response values and captured samples.
- Cleanup — stages removed, session destroyed (automatic).
x-varianz-id see them. Production traffic flows through unmodified.
What must be running
Varianz tests are true integration tests. Three things must be up and connected to each other:- A registry — see Running the registry locally.
- Your instrumented service(s) — connected to that registry, with
VARIANZ_ENABLED=true(plus, for Java/Kotlin, the agent attached), andVARIANZ_INSECURE_ALLOW_PLAINTEXT=trueif the registry is plaintext. - The test process — connected to the same registry via the framework fixture, with the same two environment variables. Test fixtures fail fast with a clear error if the SDK is disabled.
The sync barrier
await_sync_or_fail(timeout) (awaitSyncOrFail/AwaitSyncOrFail) blocks until every currently connected subscriber whose VPoints match your stages confirms receipt, and fails the test on timeout or on stage-validation errors. Always call it between inserting stages and triggering the service — insertion is asynchronous, and without the barrier your trigger can race ahead of stage delivery.
Zero subscribers
A result of “0 of 0 subscribers confirmed” means no connected service matched your stages — usually the service under test isn’t connected to the registry yet. The barrier treats this as satisfied (a lazily-registered VPoint only appears at the very call the barrier guards, so waiting here would deadlock against it), and the session re-checks at teardown, printing a
[varianz] WARNING if nothing ever received your stages. When a stage doesn’t apply, look for that warning first — it points straight at the disconnected service.0 of N or times out with some subscribers missing, one of your services is connected but didn’t register the targeted VPoint — check the VPoint name (case-sensitive, including namespace prefixes) and that the service actually reached its registration code.
Lazy VPoints
With typed schemas — annotations, the scanner, or codegen, which is the recommended setup — VPoints register at startup and stages work from the very first request; nothing in this section applies. A VPoint falls back to lazy registration only when its schema can’t be derived statically (an unannotated Python function, anopaque= parameter): it registers on its first call instead of at startup.
Testing a lazily-registered VPoint takes one extra line — trigger it once (without a session ID) so it registers, then insert stages as usual:
Insertion vs. delivery
Inserting a stage resolves its routing target against the registry’s catalog. By default insertion is lazy: a target that matches nothing yet is stored unresolved and attaches when a matching VPoint appears — convenient for lazily-registered VPoints, but it means insert success alone doesn’t prove routing. Resolution is sticky: once inserted, a stage never migrates to a different VPoint. The sync barrier is the delivery check — it confirms subscribers connected at that moment, never future ones. The practical recipe: insert →await_sync_or_fail → trigger, with warmups for anything lazy.
Triggering with the session ID
- HTTP: set the
x-varianz-idheader. From Python, use arequests.Session()with the header on the session object — barerequests.post(headers=...)loses headers on redirects. - gRPC: set
x-varianz-idmetadata on the call. - Browser (Playwright): the fixture sets the header on the
pagefor you.
Choose your framework
The test language is independent of the service language — see Test across services.
