Skip to content

assume: and observe:

EventFlow uses natural language constructs for test doubles: assume: to control behavior, observe: to watch without changing.

assume: System

assume: changes behavior for the test. Guards return specified values, actions behave as specified.

Core Principles

  1. Isolated - Each test has its own assumptions, no leakage
  2. Partial by default - Only specified behaviors change, rest runs normally
  3. Natural language - Reference by readable action/guard text

Guard Assumptions

Force guards to return specific values:

flow
assume:
  // Force guard result
  ? customer is premium = true
  ? cart is valid = false
  ? payment successful = false

  // Any comparison
  ? $total > {any} = true

  // Pattern matching
  ? customer has * = true              // All "customer has X" guards

Action Assumptions

Control how actions behave:

flow
assume:
  // Success
  validate cart = success

  // Return value
  process payment returns { transaction_id: "TXN-123" }

  // Throw error
  process payment throws "Connection timeout"
  process payment throws { code: 500, message: "Server error" }

  // Conditional (partial)
  process payment with { amount: > 10000 } triggers :fraud_review
  process payment with { amount: <= 10000 } = success

Targeting Strategies

When multiple similar actions/guards exist:

Simple Name (when unique)

flow
assume:
  process payment throws "Error"

Full Text (when ambiguous)

flow
assume:
  send confirmation email to @customer throws "SMTP error"
  send notification to @warehouse = success

Pattern Matching

flow
assume:
  send * to @customer throws "SMTP error"    // Any send to customer

Event Scope

flow
assume:
  in :checkout
    send email throws "Error"
  in :refund
    send email = success

Parameter Matching

flow
assume:
  process payment with { amount: > 10000 } triggers :fraud_review
  send email with { template: "reminder" } throws "Not configured"

observe: System

observe: watches actions without changing behavior. Real code runs, but calls are recorded for verification.

Core Principles

  1. Real execution - Action runs normally
  2. Observation only - Doesn't change behavior
  3. Verification - Check if/how action was called

Basic Usage

flow
test:
  notifications sent correctly:
    observe:
      send confirmation email
      update inventory

    // Was it called?
    = send confirmation email was called
    = update inventory was not called

    // How many times?
    = send confirmation email was called 1 time
    = send confirmation email was called at least 2 times

    // With what parameters?
    = send confirmation email was called with:
        to: @customer.email
        subject: contains "Order Confirmed"

    // Call order
    = send confirmation email was called before update inventory

Combining assume: and observe:

Use both when you need to control some behaviors and verify others:

flow
test:
  payment fails - no email sent:
    assume:
      process payment throws "Declined"
    observe:
      send confirmation email
      update inventory
    = order is in #payment_failed
    = send confirmation email was not called
    = update inventory was not called

  successful payment - verify notifications:
    assume:
      process payment returns { transaction_id: "TXN-123" }
    observe:
      send confirmation email
    = order is in #paid
    = send confirmation email was called with:
        transaction_id: "TXN-123"

Complete Example

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

  for scenario: complete checkout

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

      email fails but order succeeds:
        assume:
          send confirmation email throws "SMTP error"
        observe:
          send pick notification to @warehouse
        = order is in #confirmed
        = send pick notification to @warehouse was called
        = email failure was logged

      fraud check for high value:
        with context:
          $total is 5000
        assume:
          ? fraud detected = false
        observe:
          check fraud
        = check fraud was called
        = order is in #confirmed

assume: vs observe:

Featureassume:observe:
Changes behaviorYesNo
Real code runsNo (replaced)Yes
Records callsNoYes
Use caseForce specific outcomesVerify side effects

When to use assume:

  • Testing error paths
  • Forcing guard branches
  • Simulating external failures
  • Testing with specific return values

When to use observe:

  • Verifying side effects occur
  • Checking call parameters
  • Ensuring actions are NOT called
  • Validating call order

Assertion Patterns for observe:

flow
// Basic call check
= action was called
= action was not called

// Call count
= action was called 3 times
= action was called at least 1 time
= action was called at most 5 times

// Parameters
= action was called with:
    param1: "value"
    param2: contains "partial"
    param3: > 100

// Order
= action1 was called before action2
= action1 was called after action2

// Actor-specific
= send email to @customer was called
= emit :notification to @warehouse was called

Released under the MIT License.