Skip to content

Test Commands

Commands for running flow scenarios and binding tests.

eventflow test

Run flow scenarios as tests.

bash
# Test a single flow file
eventflow test order.flow

# Test flow with test variations
eventflow test order.flow order.test.flow

# Test specific scenario
eventflow test order.flow --scenario="successful checkout"

# Test all flows in directory
eventflow test ./flows/

# Include binding unit tests
eventflow test order.flow --with-bindings

# Watch mode
eventflow test order.flow --watch

# Verbose output
eventflow test order.flow --verbose

# Output formats
eventflow test order.flow --format=pretty   # Default
eventflow test order.flow --format=json
eventflow test order.flow --format=junit

Options:

OptionDescription
--scenario=NAMERun only the named scenario
--with-bindingsAlso run linked binding unit tests
--watchRe-run tests on file changes
--verboseShow detailed output
--format=FORMATOutput format: pretty, json, junit
--filter=PATTERNFilter scenarios by pattern

Output: Success (All Pass)

$ eventflow test order.flow order.test.flow

EventFlow v1.0.0

Loading flows...
  → order.flow (1 machine, 3 scenarios)
  → order.test.flow (6 test variations)

@order / checkout
────────────────────────────────────────

  happy path
    ✓ checkout → awaiting_payment (18ms)
    ✓ payment_success → confirmed (21ms)

  :checkout variations
    ✓ empty cart rejected (12ms)
    ✓ guest user redirected to login (10ms)

  :payment_success variations
    ✓ confirmation email is sent (14ms)

  :payment_failed variations
    ✓ payment failure notifies customer (11ms)

  :retry_checkout variations
    ✓ retry after payment failure (16ms)
    ✓ retry limit exceeded (13ms)
    ✓ admin can override retry limit (12ms)

────────────────────────────────────────
✓ 9 passing (127ms)

Output: Failure (Some Fail)

$ eventflow test order.flow order.test.flow

EventFlow v1.0.0

Loading flows...
  → order.flow (1 machine, 3 scenarios)
  → order.test.flow (6 test variations)

@order / checkout
────────────────────────────────────────

  happy path
    ✓ checkout → awaiting_payment (18ms)
    ✗ payment_success → confirmed (21ms)

      Expected: order is in #confirmed
      Actual:   order is in #awaiting_payment

      Flow trace:
        1. on :checkout from @customer
           → order moves to #awaiting_payment ✓
        2. on :payment_success from @payment
           → order moves to #confirmed ✗
              Guard 'payment is valid' returned false

      at order.flow:45

  :checkout variations
    ✓ empty cart rejected (12ms)
    ✗ guest user redirected to login (10ms)

      Expected: @customer received :login_redirect
      Actual:   No events emitted to @customer

      at order.test.flow:23

────────────────────────────────────────
✓ 2 passing
✗ 2 failing
○ 5 skipped (dependency failed)

Total: 9 scenarios (127ms)

Output: Missing Binding

$ eventflow test order.flow

EventFlow v1.0.0

Loading flows...
  → order.flow (1 machine, 3 scenarios)

@order / checkout
────────────────────────────────────────

  happy path
    ✗ checkout → awaiting_payment

      Error: No binding found for guard 'cart is not empty'

      The flow references a guard that has no PHP implementation:

        ? cart is not empty           ← Missing binding
          order moves to #awaiting_payment

      Hint: Create a guard class with:

        #[Guard('cart is not empty')]
        class CartNotEmptyGuard extends GuardBehavior
        {
            public function __invoke(array $context): bool
            {
                // TODO: Implement
            }
        }

      Or run: eventflow make:guard "cart is not empty"

      at order.flow:12

────────────────────────────────────────
✗ 1 failing (missing binding)
○ 8 skipped

Total: 9 scenarios (23ms)

Output: With Bindings (--with-bindings)

$ eventflow test order.flow --with-bindings

EventFlow v1.0.0

Loading flows...
  → order.flow (1 machine, 3 scenarios)

Loading bindings...
  → 6 guards, 4 actions, 3 events (13 total)
  → 8 test classes linked

════════════════════════════════════════
FLOW TESTS
════════════════════════════════════════

@order / checkout
────────────────────────────────────────

  happy path
    ✓ checkout → awaiting_payment (18ms)
    ✓ payment_success → confirmed (21ms)

  :checkout variations
    ✓ empty cart rejected (12ms)
    ✓ guest user redirected to login (10ms)

────────────────────────────────────────
✓ 4 passing (61ms)

════════════════════════════════════════
BINDING TESTS
════════════════════════════════════════

Guards
────────────────────────────────────────
  CartNotEmptyGuard ('cart is not empty')
    ✓ returns false when cart is null (2ms)
    ✓ returns false when cart is empty (1ms)
    ✓ returns true when cart has items (1ms)

  CustomerLoggedInGuard ('@customer is logged in')
    ✓ returns false when customer is null (2ms)
    ✓ returns false when not authenticated (1ms)
    ✓ returns true when authenticated (1ms)

Actions
────────────────────────────────────────
  IncrementRetryCountAction ('$retry_count increases by 1')
    ✓ increments retry count (1ms)
    ✓ defaults to one when not set (1ms)

────────────────────────────────────────
✓ 8 passing (11ms)

════════════════════════════════════════
SUMMARY
════════════════════════════════════════
  Flow scenarios:  4 passing
  Binding tests:   8 passing
  ────────────────────────────
  Total:          12 passing (72ms)

Output: Watch Mode

$ eventflow test order.flow --watch

EventFlow v1.0.0 (watch mode)

Watching for changes...
  → order.flow
  → order.test.flow
  → App\Order\Guards\*
  → App\Order\Actions\*

Press Ctrl+C to stop

────────────────────────────────────────
[14:32:15] Running tests...

@order / checkout
  ✓ 9 passing (127ms)

[14:32:45] File changed: CartNotEmptyGuard.php
[14:32:45] Running tests...

@order / checkout
  ✓ 9 passing (125ms)

[14:33:12] File changed: order.flow
[14:33:12] Running tests...

@order / checkout
  ✓ 9 passing (128ms)

Output: JSON Format

json
{
  "version": "1.0.0",
  "timestamp": "2024-01-15T14:32:15Z",
  "duration_ms": 127,
  "flows": ["order.flow", "order.test.flow"],
  "summary": {
    "total": 9,
    "passing": 9,
    "failing": 0,
    "skipped": 0
  },
  "machines": [
    {
      "name": "@order",
      "scenarios": [
        {
          "name": "checkout",
          "group": "happy path",
          "status": "passed",
          "duration_ms": 18,
          "assertions": [
            {
              "expression": "order is in #awaiting_payment",
              "passed": true
            }
          ]
        }
      ]
    }
  ]
}

Output: JUnit Format

xml
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="EventFlow" tests="9" failures="0" time="0.127">
  <testsuite name="@order / checkout" tests="9" failures="0" time="0.127">
    <testcase name="checkout → awaiting_payment" classname="order.checkout.happy_path" time="0.018"/>
    <testcase name="payment_success → confirmed" classname="order.checkout.happy_path" time="0.021"/>
    <testcase name="empty cart rejected" classname="order.checkout.checkout_variations" time="0.012"/>
  </testsuite>
</testsuites>

eventflow test:binding

Run unit tests for a specific binding pattern.

bash
# Test by pattern
eventflow test:binding "cart is not empty"
eventflow test:binding "@customer is logged in"
eventflow test:binding "send confirmation email"

# Test by class name
eventflow test:binding CartNotEmptyGuard

# Verbose output
eventflow test:binding "cart is not empty" --verbose

Output: Success

$ eventflow test:binding "cart is not empty"

EventFlow v1.0.0

Testing binding: 'cart is not empty'
────────────────────────────────────────

  Binding: App\Order\Guards\CartNotEmptyGuard
  Test:    Tests\Order\Guards\CartNotEmptyGuardTest

  ✓ test_returns_false_when_cart_is_null (2ms)
  ✓ test_returns_false_when_cart_is_empty (1ms)
  ✓ test_returns_true_when_cart_has_items (1ms)

────────────────────────────────────────
✓ 3 passing (4ms)

Output: Failure

$ eventflow test:binding "cart is not empty"

EventFlow v1.0.0

Testing binding: 'cart is not empty'
────────────────────────────────────────

  Binding: App\Order\Guards\CartNotEmptyGuard
  Test:    Tests\Order\Guards\CartNotEmptyGuardTest

  ✓ test_returns_false_when_cart_is_null (2ms)
  ✗ test_returns_false_when_cart_is_empty (3ms)

    Expected: false
    Actual:   true

    $cart = new \stdClass();
    $cart->items = [];
    $context = ['cart' => $cart];

    $this->assertFalse(($this->guard)($context));

                       Returns true instead of false

    at tests/Order/Guards/CartNotEmptyGuardTest.php:34

  ✓ test_returns_true_when_cart_has_items (1ms)

────────────────────────────────────────
✓ 2 passing
✗ 1 failing

Output: Binding Not Found

$ eventflow test:binding "cart is not empty"

EventFlow v1.0.0

Testing binding: 'cart is not empty'
────────────────────────────────────────

  ✗ Error: No binding found for pattern 'cart is not empty'

  Searched in:
    → App\Order\Guards
    → App\Order\Actions
    → App\Checkout\Guards

  Hint: Create a guard class with:

    #[Guard('cart is not empty')]
    class CartNotEmptyGuard extends GuardBehavior
    {
        public function __invoke(array $context): bool
        {
            // TODO: Implement
        }
    }

  Or run: eventflow make:guard "cart is not empty"

Output: Test Not Found

$ eventflow test:binding "cart is not empty"

EventFlow v1.0.0

Testing binding: 'cart is not empty'
────────────────────────────────────────

  Binding: App\Order\Guards\CartNotEmptyGuard
  Test:    Not linked

  ⚠ Warning: No test class linked to this binding

  The binding exists but has no #[TestedBy] attribute
  and no test class with #[Tests('cart is not empty')]

  Hint: Create a test class with:

    #[Tests('cart is not empty')]
    class CartNotEmptyGuardTest extends TestCase
    {
        public function test_example(): void
        {
            $guard = new CartNotEmptyGuard();
            // Add assertions
        }
    }

  Or run: eventflow make:binding-test "cart is not empty"

Output: Verbose Mode

$ eventflow test:binding "cart is not empty" --verbose

EventFlow v1.0.0

Testing binding: 'cart is not empty'
────────────────────────────────────────

  Binding Details:
    Pattern:   'cart is not empty'
    Type:      Guard
    Class:     App\Order\Guards\CartNotEmptyGuard
    File:      src/Order/Guards/CartNotEmptyGuard.php
    TestedBy:  CartNotEmptyGuardTest::class

  Test Details:
    Class:     Tests\Order\Guards\CartNotEmptyGuardTest
    File:      tests/Order/Guards/CartNotEmptyGuardTest.php
    Tests:     #[Tests('cart is not empty')]

Running tests...

  ✓ test_returns_false_when_cart_is_null (2ms)
      Input:  ['cart' => null]
      Output: false

  ✓ test_returns_false_when_cart_is_empty (1ms)
      Input:  ['cart' => {items: []}]
      Output: false

  ✓ test_returns_true_when_cart_has_items (1ms)
      Input:  ['cart' => {items: [{id: 1}]}]
      Output: true

────────────────────────────────────────
✓ 3 passing (4ms)

Coverage:
  Lines:      12/12 (100%)
  Branches:   4/4 (100%)

eventflow test:bindings

Run unit tests for all bindings.

bash
# All bindings
eventflow test:bindings

# Bindings for a specific flow
eventflow test:bindings order.flow

# Filter by type
eventflow test:bindings --type=guard
eventflow test:bindings --type=action
eventflow test:bindings --type=event

# Specific namespace
eventflow test:bindings --namespace="App\\Order"

Options:

OptionDescription
--type=TYPEFilter by binding type: guard, action, event
--namespace=NSFilter by namespace
--coverageShow coverage report

Output: All Bindings

$ eventflow test:bindings

EventFlow v1.0.0

Loading bindings...
  → 6 guards, 4 actions, 3 events (13 total)
  → 11 test classes linked

════════════════════════════════════════
GUARDS
════════════════════════════════════════

CartNotEmptyGuard ('cart is not empty')
  ✓ test_returns_false_when_cart_is_null (2ms)
  ✓ test_returns_false_when_cart_is_empty (1ms)
  ✓ test_returns_true_when_cart_has_items (1ms)

CustomerLoggedInGuard ('@customer is logged in')
  ✓ test_returns_false_when_customer_is_null (2ms)
  ✓ test_returns_false_when_not_authenticated (1ms)
  ✓ test_returns_true_when_authenticated (1ms)

RetryLimitGuard ('$retry_count is less than 3')
  ✓ test_returns_true_when_retry_count_is_zero (1ms)
  ✓ test_returns_true_when_below_limit (1ms)
  ✓ test_returns_false_when_at_limit (1ms)
  ✓ test_returns_false_when_above_limit (1ms)
  ✓ test_defaults_to_zero (1ms)

CustomerIsAdminGuard ('@customer is admin')
  ✓ test_returns_false_when_customer_is_null (1ms)
  ✓ test_returns_false_when_not_admin (1ms)
  ✓ test_returns_true_when_admin (1ms)

PaymentAuthorizedGuard ('payment is authorized')
  ⚠ No tests linked

OrderInStateGuard ('order is in {state}')
  ✓ test_returns_true_when_in_state (1ms)
  ✓ test_returns_false_when_not_in_state (1ms)

════════════════════════════════════════
ACTIONS
════════════════════════════════════════

IncrementRetryCountAction ('$retry_count increases by 1')
  ✓ test_increments_retry_count (1ms)
  ✓ test_defaults_to_one_when_not_set (1ms)

SendConfirmationEmailAction ('send confirmation email')
  ✓ test_sends_confirmation_email (3ms)
  ✓ test_handles_missing_customer_gracefully (1ms)

LogPaymentAttemptAction ('log payment attempt')
  ✓ test_logs_attempt (2ms)

NotifyCustomerAction ('notify customer')
  ⚠ No tests linked

════════════════════════════════════════
EVENTS
════════════════════════════════════════

CheckoutEvent (':checkout')
  ✓ test_validates_payload (1ms)

PaymentRequestEvent (':payment_request')
  ✓ test_includes_order_id (1ms)
  ✓ test_includes_amount (1ms)

OrderConfirmedEvent (':order_confirmed')
  ⚠ No tests linked

════════════════════════════════════════
SUMMARY
════════════════════════════════════════

  Guards:   14/16 tests passing (4 bindings, 1 untested)
  Actions:   5/5 tests passing (3 bindings, 1 untested)
  Events:    3/3 tests passing (2 bindings, 1 untested)
  ─────────────────────────────────────────────────────
  Total:    22/24 tests passing (72ms)

  ⚠ 3 bindings have no test coverage:
    → PaymentAuthorizedGuard
    → NotifyCustomerAction
    → OrderConfirmedEvent

Output: For Flow File

$ eventflow test:bindings order.flow

EventFlow v1.0.0

Analyzing order.flow...
  → 4 guards required
  → 2 actions required
  → 3 events required

Testing bindings used in order.flow...

════════════════════════════════════════
GUARDS (used in order.flow)
════════════════════════════════════════

CartNotEmptyGuard ('cart is not empty')
  ✓ 3 tests passing

CustomerLoggedInGuard ('@customer is logged in')
  ✓ 3 tests passing

RetryLimitGuard ('$retry_count is less than 3')
  ✓ 5 tests passing

CustomerIsAdminGuard ('@customer is admin')
  ✓ 3 tests passing

════════════════════════════════════════
ACTIONS (used in order.flow)
════════════════════════════════════════

IncrementRetryCountAction ('$retry_count increases by 1')
  ✓ 2 tests passing

SendConfirmationEmailAction ('send confirmation email')
  ✓ 2 tests passing

════════════════════════════════════════
SUMMARY
════════════════════════════════════════
  Bindings for order.flow: 6/6 implemented
  Tests: 18/18 passing (32ms)

Released under the MIT License.