Skip to main content
Every Varianz test, in every framework, follows the same six steps:
  1. A test session is created (automatically, by the fixture or extension).
  2. Stages are inserted — CEL expressions bound to specific VPoints.
  3. Sync — the test waits until connected services confirm they received the stages.
  4. Trigger — the test calls the service, passing the session ID.
  5. Assert — on response values and captured samples.
  6. Cleanup — stages removed, session destroyed (automatic).
Stages are session-scoped: only requests carrying the matching 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:
  1. A registry — see Running the registry locally.
  2. Your instrumented service(s) — connected to that registry, with VARIANZ_ENABLED=true (plus, for Java/Kotlin, the agent attached), and VARIANZ_INSECURE_ALLOW_PLAINTEXT=true if the registry is plaintext.
  3. 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 test inserts stages into the registry; the service receives them over its subscription; the trigger request carries the session ID; the VPoint applies the stages; samples stream back to the test.

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.
If sync reports 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, an opaque= 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:
To skip the extra step entirely, add type annotations or wire up build integration — the VPoint then registers statically.

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-id header. From Python, use a requests.Session() with the header on the session object — bare requests.post(headers=...) loses headers on redirects.
  • gRPC: set x-varianz-id metadata on the call.
  • Browser (Playwright): the fixture sets the header on the page for you.
The session must propagate through every hop of a multi-service flow.

Choose your framework

The test language is independent of the service language — see Test across services.