Module: Funes::ProjectionTestHelper

Extended by:
ActiveSupport::Concern
Defined in:
app/helpers/funes/projection_test_helper.rb

Overview

Test helper for exercising projections in isolation.

Include this module in your test class to access methods that interpret a single event, build the initial state, or apply the final state of a projection — without processing an entire event stream.

Bind the projection class once with the projection macro and then call #interpret, #initial_state, or #final_state without repeating it.

Examples:

Bind the projection at the class level

class InventorySnapshotProjectionTest < ActiveSupport::TestCase
  include Funes::ProjectionTestHelper
  projection InventorySnapshotProjection

  test "receiving items increases quantity on hand" do
    state = InventorySnapshot.new(quantity_on_hand: 10)
    event = Inventory::ItemReceived.new(quantity: 5, unit_cost: 9.99)

    result = interpret(event, given: state)

    assert_equal 15, result.quantity_on_hand
  end
end

Override the bound projection per call

interpret(event, given: state, projection: AnotherProjection)

Instance Method Summary collapse

Instance Method Details

#final_state(given:, at: Time.current, projection: nil) ⇒ ActiveModel::Model, ActiveRecord::Base

Applies the final-state transformation of the bound projection.

Examples:

assert_equal 33.33, final_state(given: OrderSnapshot.new(total: 100, item_count: 3)).average_item_price

Parameters:

  • given (ActiveModel::Model, ActiveRecord::Base)

    The state to finalize.

  • at (Time, Date) (defaults to: Time.current)

    The temporal reference point. Defaults to Time.current.

  • projection (Class<Funes::Projection>, nil) (defaults to: nil)

    Overrides the class bound via projection.

Returns:

  • (ActiveModel::Model, ActiveRecord::Base)

    The state after the final_state block.



93
94
95
96
97
# File 'app/helpers/funes/projection_test_helper.rb', line 93

def final_state(given:, at: Time.current, projection: nil)
  projection_class = resolve_projection(projection)
  projection_class.instance_variable_get(:@interpretations)[:final]
                  .call(given, coerce_at(at))
end

#initial_state(at: Time.current, projection: nil) ⇒ ActiveModel::Model, ActiveRecord::Base

Builds the initial state of the bound projection.

Examples:

assert_equal 0, initial_state.quantity_on_hand

Parameters:

  • at (Time, Date) (defaults to: Time.current)

    The temporal reference point. Defaults to Time.current.

  • projection (Class<Funes::Projection>, nil) (defaults to: nil)

    Overrides the class bound via projection.

Returns:

  • (ActiveModel::Model, ActiveRecord::Base)

    The state produced by the initial_state block.



78
79
80
81
82
# File 'app/helpers/funes/projection_test_helper.rb', line 78

def initial_state(at: Time.current, projection: nil)
  projection_class = resolve_projection(projection)
  projection_class.instance_variable_get(:@interpretations)[:init]
                  .call(projection_class.instance_variable_get(:@materialization_model), coerce_at(at))
end

#interpret(event, given:, at: Time.current, projection: nil) ⇒ ActiveModel::Model, ActiveRecord::Base

Interprets a single event in isolation and returns the resulting state.

If the event carries an occurred_at, it takes precedence over at —mirroring how Funes::Projection replays persisted events. A Date passed as at is coerced to its beginning_of_day.

Examples:

result = interpret(Order::ItemAdded.new(amount: 50), given: OrderSnapshot.new(total: 100))
assert_equal 150, result.total

Parameters:

  • event (Funes::Event)

    The event to interpret.

  • given (ActiveModel::Model, ActiveRecord::Base)

    The state before applying the event.

  • at (Time, Date) (defaults to: Time.current)

    The temporal reference point. Defaults to Time.current.

  • projection (Class<Funes::Projection>, nil) (defaults to: nil)

    Overrides the class bound via projection.

Returns:

  • (ActiveModel::Model, ActiveRecord::Base)

    The state after the interpretation.



62
63
64
65
66
67
68
# File 'app/helpers/funes/projection_test_helper.rb', line 62

def interpret(event, given:, at: Time.current, projection: nil)
  projection_class = resolve_projection(projection)
  coerced_at = coerce_at(at)
  event_at = event.occurred_at || coerced_at
  projection_class.instance_variable_get(:@interpretations)[event.class]
                  .call(given, event, event_at)
end