Skip to content

Test Files (.test.flow) ​

EventFlow uses a two-file paradigm for testing: .flow files contain the happy path, .test.flow files contain variations.

Philosophy ​

Happy path is the story. Tests are "what if" questions.

The main .flow file tells the successful journey. Test files ask questions: "What if the cart is empty?", "What if payment fails?", "What if the gateway is down?"

File Structure ​

order.flow              <- Happy path (clean, readable)
order.test.flow         <- Variations (edge cases, failures)

For machine systems:

checkout/
  system.flow           <- System happy path
  system.test.flow      <- System-level tests
  order.flow            <- Order machine
  order.test.flow       <- Order machine tests
  payment.flow          <- Payment machine
  payment.test.flow     <- Payment machine tests

Happy Path (.flow) ​

The flow file contains the expected, successful journey with optional expect: assertions:

flow
// order.flow
machine: @order

scenario: checkout

  given:
    @customer is logged in
    cart has items

  on :checkout from @customer (api)
    validate cart
    ? cart is valid
      emit :payment_request to @payment
      order moves to #awaiting_payment

    expect:
      = order is in #awaiting_payment

  on :payment_success from @payment
    send confirmation email
    order moves to #confirmed

  expect:
    = order is in #confirmed
    = @customer received :confirmation

Key points:

  • Clean, readable flow
  • expect: blocks verify happy path outcomes
  • No test variations - just the story

Variations (.test.flow) ​

The test file asks "what if" questions:

flow
// order.test.flow
test: @order

  for scenario: checkout

    for :checkout:
      empty cart rejected:
        with scenario:
          cart is empty
        = @customer received :error

      invalid cart rejected:
        assume:
          ? cart is valid = false
        = @customer received :error

    payment fails:
      after :checkout
      receive :payment_failed from @payment
      = order is in #payment_failed

Two Test Levels ​

Transition Tests (for :event:) ​

Test individual event handlers in isolation:

flow
for :checkout:
  // Tests for :checkout handler

  empty cart:
    with scenario:
      cart is empty
    = @customer received :error

  payment gateway down:
    assume:
      ? payment_gateway is available = false
    = order is in #queued

Use for:

  • Guard branch coverage
  • Action behavior verification
  • Edge case handling
  • Unit-level testing

Scenario Tests ​

Test full flow variations with path divergence:

flow
payment fails:
  after :checkout
  receive :payment_failed from @payment
  = order is in #payment_failed

retry then succeed:
  after :checkout
  receive :payment_failed from @payment
  then :payment_success from @payment
  = order is in #confirmed

Use for:

  • Alternative paths
  • Multi-event sequences
  • Integration-level testing

Assertions ​

In flow files, assertions go inside expect: blocks:

flow
// order.flow
on :checkout from @customer (api)
  order moves to #awaiting_payment

  expect:
    = order is in #awaiting_payment

In test files, assertions go directly under test names:

flow
// order.test.flow
empty cart rejected:
  with scenario:
    cart is empty
  = @customer received :error

Complete Example ​

order.flow ​

flow
machine: @order

scenario: complete checkout

  given:
    @customer is logged in
    cart has items
    $total: number is 100

  on :checkout from @customer (api)
    validate cart
    ? cart is valid
      emit :payment_request to @payment
      order moves to #awaiting_payment
    otherwise
      emit :error to @customer

    expect:
      = order is in #awaiting_payment

  on :payment_success from @payment
    reserve inventory
    send confirmation email
    order moves to #confirmed

  on :payment_failed from @payment
    $retry_count increases by 1
    ? $retry_count < 3
      emit :payment_request to @payment
    otherwise
      order moves to #failed
      emit :order_failed to @customer

  expect:
    = order is in #confirmed
    = @customer received :confirmation

order.test.flow ​

flow
test: @order

  for scenario: complete checkout

    for :checkout:
      empty cart rejected:
        with scenario:
          cart is empty
        = @customer received :error

      high value triggers fraud check:
        with context:
          $total is 5000
        observe:
          check fraud
        = check fraud was called

    for :payment_failed:
      first failure retries:
        with context:
          $retry_count is 0
        = :payment_request was emitted to @payment

      third failure gives up:
        with context:
          $retry_count is 2
        = order is in #failed
        = @customer received :order_failed

    payment timeout then retry:
      after :checkout
      receive :payment_timeout from @payment
      then :payment_success from @payment
      = order is in #confirmed

    cancel during payment:
      after :checkout
      receive :cancel from @customer
      = order is in #cancelled

Released under the MIT License.