Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Testing

Testing

Caches and events

On this page

An event is a fact an action records, such as "an order was placed", and a subscriber is code that runs when one is delivered (Subscribers). Events run after the action that emits them commits. In a test, what a step emits is delivered before the next line runs, so a test checks an event's effects on the line after the call:

  • Events are delivered after each step of the test commits and before the next statement runs, at the test's clock, until no events are left. A test checks their effects, such as a sent reminder in requests. A subscriber that fails fails the test, as below.
  • Caches are copies used only by display reads from routes (Caches). In a test they are on and start empty, in an in-memory cache that behaves like Redis, so a route test sees the same invalidation as production. Tests can't read, fill or clear a cache directly. Actions never read caches, so only route tests meet them.

Steps and delivery

A step is a call of an action or fixture, a route test's request, a schedule's .run() or clock.advance(...). Once a step comes back, what is due in the test's outbox, the list of events waiting to be delivered, is delivered as the runner delivers it anywhere: as the system, limited to its event's tenant. Then what those subscribers emit is delivered in turn, until nothing is due.

The order subscribers run in

Subscribers run in an order shuffled by the run's seed, the one the report prints and --seed gives again, mixed with the test's own identity. Each delivery is drawn from all those due at that moment. So a test that relies on the order its subscribers run in fails under some seed, and rerunning it with that seed fails the same way.

Retries

A delivery whose subscriber fails in a way worth trying again waits its full backoff: 30 seconds, and then twice as long each time. There is no random jitter, since a test has no randomness to draw it from (Retries and giving up). The test's clock moves only when the test moves it, so the retry runs once clock.advance reaches it.

A fake failing a subscriber's call to a provider is retried and given up the same way, and a test scripts it with fakes:. A subscriber's own failure worth retrying tests its retries too.

A delivery that fails for good

A delivery fails for good when its failure isn't worth retrying, when it fails on its last attempt, or when it hits a fault. It ends the test there as test.subscriber_failed, naming the event, the subscriber and the attempt.

What a route's settle: delivers while its request waits belongs to the request, and the response says how it ended (Waiting for follow-on work).

An example

Here placing an order emits OrderPlaced, and cook_placed cooks it once a chef is on shift:

example
use kitchen/entities { Order, OrderNotFound }use kitchen/failures { NoChef }use kitchen/queries { chefs }/// Cooks an order, once a chef is on shift.action cook(order_id: Order.id) fails OrderNotFound, NoChef -> Unit {    if (list.count(chefs()) == 0) {        reject NoChef {}    }    attempt Order.update(order_id) {        set: {            cooked_at: clock.now        }    }}
example
use kitchen/actions { cook }use kitchen/events { OrderPlaced }/// Cooks each order placed.subscriber cook_placed {    on: OrderPlaced    run: cook(event.order_id)}
example
use kitchen/entities { Chef, Order }use kitchen/actions { start_shift }test "an order is cooked before the next line" {    clock: "2026-10-20T18:00:00Z"    var chef: Chef = start_shift()    assert chef.on_shift    var order: Order = place_order()    var cooked: Order = attempt Order.get(order.id)    assert cooked.cooked_at == "2026-10-20T18:00:00Z"}test "an order placed before the shift is cooked when its retry comes round" {    clock: "2026-10-20T18:00:00Z"    var order: Order = place_order()    var chef: Chef = start_shift()    assert chef.on_shift    var waiting: Order = attempt Order.get(order.id)    assert waiting.cooked_at == none    clock.advance(30s)    var cooked: Order = attempt Order.get(order.id)    assert cooked.cooked_at == "2026-10-20T18:00:30Z"}

The first test reads the order cooked on the line after placing it. In the second, the delivery fails with NoChef, which is worth retrying, so it waits 30 seconds. Starting the shift doesn't move the clock, and the retry runs once clock.advance(30s) reaches it.

Not built yet: caches, which need the Redis driver, and running a batch to the end in a test. A batch run a step starts is left as it was started (Provider calls and capabilities in tests).

Markdown for agents: /docs/caches-and-events.md.Built from Jadpo main at a707efb.