Module: GenevaDrive::TestHelpers

Defined in:
lib/geneva_drive/test_helpers.rb

Overview

Test helpers for testing workflows in your application.

Include this module in your test classes to get access to workflow testing utilities.

Examples:

Using in a test

class SignupWorkflowTest < ActiveSupport::TestCase
  include GenevaDrive::TestHelpers

  test "completes all steps" do
    workflow = SignupWorkflow.create!(hero: users(:one))
    speedrun_workflow(workflow)
    assert workflow.finished?
  end
end

Instance Method Summary collapse

Instance Method Details

#assert_step_executed(workflow, step_name, state: "completed") ⇒ void

This method returns an undefined value.

Asserts that a workflow has executed a specific step.

Examples:

Check that a step was executed

assert_step_executed(workflow, :send_email)
assert_step_executed(workflow, :optional_step, state: "skipped")

Parameters:

  • workflow (GenevaDrive::Workflow)

    the workflow to check

  • step_name (String, Symbol)

    the expected step name

  • state (String) (defaults to: "completed")

    the expected step state (default: "completed")



148
149
150
151
152
153
154
155
156
# File 'lib/geneva_drive/test_helpers.rb', line 148

def assert_step_executed(workflow, step_name, state: "completed")
  step_execution = workflow.step_executions.find_by(
    step_name: step_name.to_s,
    state: state
  )

  assert step_execution,
    "Expected step '#{step_name}' to be #{state}, but it was not found"
end

#assert_workflow_state(workflow, expected_state) ⇒ void

This method returns an undefined value.

Asserts that a workflow is in a specific state.

Examples:

Check workflow state

assert_workflow_state(workflow, :finished)
assert_workflow_state(workflow, "paused")

Parameters:

  • workflow (GenevaDrive::Workflow)

    the workflow to check

  • expected_state (String, Symbol)

    the expected state



168
169
170
171
172
# File 'lib/geneva_drive/test_helpers.rb', line 168

def assert_workflow_state(workflow, expected_state)
  workflow.reload
  assert_equal expected_state.to_s, workflow.state,
    "Expected workflow to be #{expected_state}, but was #{workflow.state}"
end

#perform_next_step(workflow) ⇒ GenevaDrive::StepExecution?

Executes only the next pending step of a workflow.

Useful for testing step-by-step behavior and inspecting state between steps.

Examples:

Execute one step at a time

workflow = PaymentWorkflow.create!(hero: order)
perform_next_step(workflow)
assert_equal "charge", workflow.current_step_name
perform_next_step(workflow)
assert_equal "receipt", workflow.current_step_name

Parameters:

Returns:



76
77
78
79
80
81
82
83
84
# File 'lib/geneva_drive/test_helpers.rb', line 76

def perform_next_step(workflow)
  workflow.reload
  step_execution = workflow.current_execution
  return nil unless step_execution

  step_execution.execute!
  workflow.reload
  step_execution
end

#perform_step_inline(workflow, step_name = nil) ⇒ GenevaDrive::StepExecution

Executes a step inline, bypassing normal workflow progression.

Creates a step execution for the given step and executes it immediately. This is a testing shortcut that allows you to test individual steps without running through the entire flow.

When called without a step name, executes the current step.

Examples:

Execute the current step

workflow = OnboardingWorkflow.create!(hero: user)
perform_step_inline(workflow)

Execute a specific step directly

workflow = OnboardingWorkflow.create!(hero: user)
perform_step_inline(workflow, :send_welcome_email)

Parameters:

  • workflow (GenevaDrive::Workflow)

    the workflow containing the step

  • step_name (String, Symbol, nil) (defaults to: nil)

    the step to execute (defaults to current step)

Returns:

Raises:

  • (ArgumentError)

    if the step is not defined in the workflow



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/geneva_drive/test_helpers.rb', line 107

def perform_step_inline(workflow, step_name = nil)
  workflow.reload
  step_name ||= workflow.next_step_name
  step_def = workflow.class.steps.find { |s| s.name == step_name.to_s }

  unless step_def
    available = workflow.class.steps.map(&:name).join(", ")
    raise ArgumentError,
      "Step '#{step_name}' is not defined in #{workflow.class.name}. " \
      "Available steps: #{available}"
  end

  # Cancel any existing scheduled step executions to satisfy uniqueness constraint
  workflow.step_executions.where(state: "scheduled").update_all(
    state: "canceled",
    outcome: "canceled",
    canceled_at: Time.current
  )

  step_execution = workflow.step_executions.create!(
    step_name: step_def.name,
    state: "scheduled",
    scheduled_for: Time.current
  )

  step_execution.execute!
  workflow.reload
  step_execution
end

#speedrun_workflow(workflow, max_iterations: 100) ⇒ GenevaDrive::Workflow

Executes all steps of a workflow synchronously for testing.

This method runs through all scheduled steps, executing each one immediately regardless of wait times. Useful for testing complete workflow behavior.

Examples:

Run a workflow to completion

workflow = OnboardingWorkflow.create!(hero: user)
speedrun_workflow(workflow)
assert_equal "finished", workflow.state

Run with custom iteration limit

speedrun_workflow(workflow, max_iterations: 10)

Parameters:

  • workflow (GenevaDrive::Workflow)

    the workflow to run

  • max_iterations (Integer) (defaults to: 100)

    maximum steps to execute (safety limit)

Returns:

Raises:

  • (RuntimeError)

    if max_iterations is exceeded



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/geneva_drive/test_helpers.rb', line 39

def speedrun_workflow(workflow, max_iterations: 100)
  iterations = 0

  loop do
    workflow.reload
    break if %w[finished canceled paused].include?(workflow.state)

    step_execution = workflow.current_execution
    break unless step_execution

    step_execution.execute!
    iterations += 1

    if iterations >= max_iterations
      raise "speedrun_workflow exceeded max_iterations (#{max_iterations}). " \
            "Workflow may be in an infinite loop."
    end
  end

  workflow.reload
end