> ## 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.

# Test across services

> Drive multi-service flows from one test: override and probe downstream services in any language, trigger through the entry point, and assert end to end.

Varianz tests are not bound to one process. A single test session can override a VPoint in one service, probe a VPoint in another, and trigger the flow through a third — because all services share the same registry and the session ID travels with the request.

## The pattern

1. **Override or probe the downstream services** you need to control or observe.
2. **Sync**, so every connected service has the stages.
3. **Trigger the flow through the entry point** — an HTTP call or gRPC request carrying `x-varianz-id`. The ID [propagates](/guides/propagate-sessions) hop by hop.
4. **Assert** on the response and on captured samples.

```python theme={null}
from datetime import timedelta
from varianz import cel

def test_checkout_sends_notification(varianz):
    # Probe the notification service (it might be Go; the test doesn't care)
    varianz.insert(cel("notification/send", 'sample("notification-sent", invoke())'))
    varianz.await_sync_or_fail(timeout_ms=5000)

    # Trigger via the entry point; the session ID flows downstream
    resp = requests.post(
        "http://localhost:8080/api/order",
        headers={"x-varianz-id": varianz.session_id},
        json=order_payload,
    )
    assert resp.status_code == 200

    varianz.assert_sample("notification-sent") \
        .within(timedelta(seconds=10)) \
        .exists()
```

Add an override on `payment/charge` and a negative assertion on the probe, and the same shape tests the failure path — see [Override and observe](/guides/override-and-observe#a-complete-example).

<Note>
  When triggering over HTTP from Python, use a `requests.Session()` with the header set on the session object. Bare `requests.post(headers=...)` drops custom headers when following redirects, which silently strips the session ID.
</Note>

## The test language doesn't have to match the service language

The protocol between every test SDK and the registry is identical: stages go in via gRPC with the session ID; samples come back the same way regardless of which runtime executed the VPoint. Pick the test harness that fits your team:

| Test harness                       | Can drive services written in                                   |
| ---------------------------------- | --------------------------------------------------------------- |
| `varianz-pytest` (Python)          | Python, TypeScript, Go, Java, Kotlin — any instrumented service |
| JUnit 5 extension (Java/Kotlin)    | any instrumented service                                        |
| `varianztest` (Go)                 | any instrumented service                                        |
| `@varianz/vitest` (TypeScript)     | any instrumented service                                        |
| `@varianz/playwright` (TypeScript) | any service reachable over HTTP, driving a real browser         |

This is useful in practice: drive a Java service from pytest without standing up a Gradle test project; probe a Go service from the Python suite that already covers the flow; keep browser tests in Playwright while the backend stages live wherever they live.

## Services without an SDK

Services in languages without a Varianz SDK (C#, PHP, Elixir, …) can't host VPoints, but they don't break the pattern:

1. Instrument the services they **call**.
2. Trigger the uninstrumented service through its normal API.
3. Override and probe the instrumented services downstream of it.

The only requirement: the uninstrumented service must **forward the `x-varianz-id` header** to its downstream calls, or the session chain breaks at that hop. That's usually a one-line addition to an existing header-forwarding list — see [Propagate sessions](/guides/propagate-sessions#services-with-explicit-header-forwarding-lists).

## Targeting when names collide

If two services register VPoints with the same short name, or you run the same service in several environments, scope the stage's routing target:

```python theme={null}
# Only checkout-service instances, only us-east-1 prod:
varianz.insert(cel(
    "checkout-service:payment/charge region=us-east-1 cluster=prod",
    'ChargeResult { declined: true }',
))
```

Targets match VPoint names by suffix on `/` boundaries and must resolve to exactly one VPoint — ambiguity is an error, and adding segments (or `org=`/`pkg=` filters) disambiguates. Full rules: [Naming and routing](/reference/naming-and-routing).
