> ## Documentation Index
> Fetch the complete documentation index at: https://docs.varianz.io/llms.txt
> Use this file to discover all available pages before exploring further.

# How tests work

> The Varianz test lifecycle: sessions, stage insertion, the sync barrier, triggering with x-varianz-id, and asserting on samples — plus the edge cases that matter.

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](/reference/local-registry).
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

<Note>
  **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.
</Note>

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:

```python theme={null}
def test_email_probe(varianz):
    # Trigger once so the lazily-registered VPoint appears in the registry.
    stub.SendOrderConfirmation(request, timeout=10)

    varianz.insert(cel("email/send-order-confirmation", 'sample("email-call", invoke())'))
    varianz.await_sync_or_fail(timeout_ms=10000)

    metadata = [("x-varianz-id", varianz.session_id)]
    stub.SendOrderConfirmation(request, metadata=metadata, timeout=10)

    varianz.assert_sample("email-call").within(timedelta(seconds=15)).exists()
```

To skip the extra step entirely, add type annotations or wire up [build integration](/guides/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](/guides/propagate-sessions) through every hop of a multi-service flow.

## Choose your framework

| Framework        | Package                                       | Page                              |
| ---------------- | --------------------------------------------- | --------------------------------- |
| pytest           | `varianz-pytest`                              | [pytest](/testing/pytest)         |
| Vitest           | `@varianz/vitest`                             | [Vitest](/testing/vitest)         |
| Playwright       | `@varianz/playwright`                         | [Playwright](/testing/playwright) |
| JUnit 5 / Kotest | `io.varianz:varianz-junit` / `varianz-kotest` | [JUnit](/testing/junit)           |
| Go `testing`     | `go.varianz.io/sdk/varianztest`               | [Go](/testing/go)                 |

The test language is independent of the service language — see [Test across services](/guides/test-across-services).
