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