---
kind: guide
zz: "0.8.0"
updated: 2026-10-10
state: live
rules: https://zriz.io/llms.txt
---
# Test an async API with polling, not sleep

Use the poll step of a zriz pipeline to wait for an async result. The test ends when the result comes, and fails if it never does.

## The problem {#problem}

Some work is done after the call ends: a queue sends a notice, a job updates a record. A test with a fixed sleep is slow when the sleep is long. It fails at random when the sleep is short.

## The test {#test}

The example is a shop API. A payment puts an event on a queue. Some time later, the user has a notice of the type `payment_completed`.

Start from a project that `zz init` made: the [quick start](https://zriz.io/docs.md) gives the commands. Add four actions to the resource file.

`.zriz/resources/target.json`:

```json
{
  "type": "http",
  "description": "Your app under test",
  "base-url": "${env.TARGET_URL}",
  "headers": { "Content-Type": "application/json" },
  "actions": {
    "check": { "method": "GET", "path": "/api/health" },
    "register": { "method": "POST", "path": "/api/auth/register" },
    "create-order": { "method": "POST", "path": "/api/orders" },
    "pay": { "method": "POST", "path": "/api/payments" },
    "notifications": { "method": "GET", "path": "/api/notifications" }
  }
}
```

`.zriz/pipelines/payment-notice.json`:

```json
{
  "description": "Pay an order, then wait for the notice",
  "steps": [
    {
      "call": "target/register",
      "body": { "email": "u-${gen.uuid}@test.com", "password": "secret123", "name": "Ann" },
      "expect": [["status", "==", 201]],
      "save": { "token": "body.token" }
    },
    {
      "call": "target/create-order",
      "headers": { "Authorization": "Bearer ${ctx.token}" },
      "body": { "items": [{ "sku": "KB-001", "quantity": 1 }] },
      "expect": [["status", "==", 201]],
      "save": { "order-id": "body.order_id", "total": "body.total" }
    },
    {
      "call": "target/pay",
      "headers": { "Authorization": "Bearer ${ctx.token}" },
      "body": { "order_id": "${ctx.order-id}", "amount": "${ctx.total}", "method": "card" },
      "expect": [["status", "==", 201]]
    },
    {
      "poll": "target/notifications",
      "headers": { "Authorization": "Bearer ${ctx.token}" },
      "until": ["body.total", ">=", 1],
      "interval-ms": 500,
      "max-attempts": 20,
      "expect": [["body.items[0].type", "==", "payment_completed"]]
    }
  ]
}
```

## Run it {#run}

```sh
zz run payment-notice
```

```text
"status":"pass"
```

## What it proves {#proves}

- `poll` with `until`: the step calls again each 500 ms until the user has a notice. A fast system ends the test fast.
- `max-attempts`: after 20 calls with no notice, the test fails. A lost event does not pass.
- `expect` on the type: the notice is the one for the payment.

> Note: `"${ctx.total}"` alone in a value keeps the number type. The API gets a number, not text.

## Next {#next}

- [Test that a missing record returns 404](https://zriz.io/learn/test-api-404-not-found.md)
- [Pipelines](https://zriz.io/docs/pipelines.md) gives each key of `poll`.
