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
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
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 gives the commands. Add four actions to the resource file.
.zriz/resources/target.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:
{
"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
zz run payment-notice
"status":"pass"
What it proves
pollwithuntil: 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.expecton 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
- Test that a missing record returns 404
- Pipelines gives each key of
poll.