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 testsHappy 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 :confirmationKey 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_failedTwo 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 #queuedUse 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 #confirmedUse 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_paymentIn test files, assertions go directly under test names:
flow
// order.test.flow
empty cart rejected:
with scenario:
cart is empty
= @customer received :errorComplete 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 :confirmationorder.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