* Add all Ruby SDK reference files (11 files, ~2100 lines) Created complete Ruby reference documentation covering: - ruby.md: Overview, quick start, key concepts, file organization - patterns.md: Signals, queries, updates, child workflows, saga, cancellation, etc. - determinism.md: Illegal call tracing, safe alternatives table - determinism-protection.md: TracePoint, durable fiber scheduler, customization - versioning.md: Patching API, type versioning, worker versioning - testing.md: WorkflowEnvironment, mocking, replay, activity testing - error-handling.md: ApplicationError, retries, timeouts, workflow failure - data-handling.md: Data converter, ActiveModel, hints, search attributes - observability.md: Logging, metrics, best practices - gotchas.md: Common mistakes, illegal call tracing issues - advanced-features.md: Schedules, async completion, worker tuning, Rails Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix alignment issues in Ruby reference files Self-review fixes: - patterns.md: Remove non-existent `workflow_run` annotation; entry point is `def execute` (no annotation needed, unlike Python's @workflow.run) - patterns.md: Remove conflicting manual query methods that duplicated workflow_query_attr_reader - error-handling.md: Remove `await` keyword (doesn't exist in Ruby) - gotchas.md: Replace TS-style CancellationScope with Ruby's Temporalio::Cancellation token-based detached cancellation - data-handling.md: Replace homemade ActiveModel mixin with official SDK pattern using ActiveSupport::Concern + ActiveModel::Serializers::JSON - data-handling.md: Fix list_workflows call signature (positional, not kw) - ruby.md, gotchas.md: Fix require paths to use 'temporalio/activity' instead of 'temporalio/activity/definition' Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix correctness issues in Ruby reference files - patterns.md: Fix external workflow signal to use class method ref (TargetWorkflow.data_ready instead of TargetWorkflow, :data_ready) - patterns.md: Add ? suffix to all_handlers_finished (Ruby boolean convention) - ruby.md: Add 'default' namespace to Client.connect calls Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add Ruby to all language references in SKILL.md and core files - SKILL.md: Add "Temporal Ruby" trigger phrase to description - SKILL.md: Update Overview to list Ruby as supported language - SKILL.md: Add Ruby entry to Getting Started guide - core/determinism.md: Add Ruby SDK Protection Mechanism entry (Illegal Call Tracing via TracePoint + Durable Fiber Scheduler) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Apply suggestions from code review Co-authored-by: Bart de Water <118401830+bdewater-thatch@users.noreply.github.com> Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com> * Apply suggestions from code review Co-authored-by: Bart de Water <118401830+bdewater-thatch@users.noreply.github.com> Co-authored-by: Donald Pinckney <donald_pinckney@icloud.com> * Apply suggestion from @chris-olszewski Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com> * copy over sample code * Remove useless section, mention Mutex * cleanup mutex mentions * Clean up transitive NDE section * Menial changes to align to python structure * Add Workflow Init section to Ruby advanced-features Document the workflow_init class method (Ruby's equivalent of Python's @workflow.init) for initializing workflow state before signal/update handlers run. Parallels the Python reference's Workflow Init section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document graceful_shutdown_period in Ruby Worker Tuning Add the graceful_shutdown_period worker option (Ruby's equivalent of Python's graceful_shutdown_timeout) to the Worker Tuning section, with an explanation of the worker shutdown sequence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Propagate cancellation in Ruby activity-error handling Update the Handling Activity Errors example to re-raise when Temporalio::Error.canceled? is true (Ruby's equivalent of Python's is_cancelled_exception), so a canceled activity cancels the workflow rather than failing it. Also clarify that only ApplicationError fails a workflow; other exceptions only fail/retry the workflow task. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Align Ruby Workflow Failure section to Python Replace the workflow_failure_exception_type / worker-option examples (misaligned with Python and already covered in advanced-features.md) with Python's example of raising an ApplicationError to deliberately fail a workflow. Add the terse note about not using non_retryable inside a workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add logger configuration to Ruby observability Document configuring the logger via Client.connect (logger: kwarg), which is used by both Temporalio::Workflow.logger and the activity logger. Parallels Python's Customizing Logger Configuration section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Make Ruby Saga compensations cancellation-proof Run saga compensations with a detached Temporalio::Cancellation so they still execute when the workflow is canceled mid-saga. Previously they used the workflow cancellation, which is already canceled at that point, so the compensation activities would be canceled before starting. This is the Ruby equivalent of Python's asyncio.shield. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Document patched() memoization caveat in Ruby versioning Note that Temporalio::Workflow.patched memoizes per patch ID, so it can't be used reliably in loops; append a sequence number to the patch ID per iteration. This behavior is shared with Python and .NET. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add default versioning behavior to Ruby worker versioning Document configuring default_versioning_behavior on Temporalio::Worker::DeploymentOptions, paralleling Python's Worker Configuration with Default Behavior section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix worker versioning config API names in Ruby docs The Configuring Workers for Versioning example used class/kwarg names that don't exist in the SDK. Correct them to deployment_options:, Temporalio::Worker::DeploymentOptions, and Temporalio::WorkerDeploymentVersion, matching the actual API and the Worker Configuration with Default Behavior example. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix worker concurrency config in Ruby Worker Tuning max_concurrent_workflow_tasks and max_concurrent_activities are not valid Worker.new kwargs. Use the tuner: option with Temporalio::Worker::Tuner.create_fixed(workflow_slots:, activity_slots:) to control concurrent execution slots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Align Ruby Workflow Init title with Python Rename the section to 'Workflow Init Decorator' to match the Python reference's heading. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Structure Ruby Metrics to match Python Split the flat Metrics section into 'Enabling SDK Metrics' and 'Key SDK Metrics' subsections, matching the Python reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Bart de Water <118401830+bdewater-thatch@users.noreply.github.com> Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com>
6.5 KiB
Ruby SDK Testing
Overview
The Temporal Ruby SDK provides testing utilities compatible with any Ruby test framework (minitest is commonly used). The two main testing classes are Temporalio::Testing::WorkflowEnvironment for end-to-end workflow testing and Temporalio::Testing::ActivityEnvironment for isolated activity testing.
Workflow Test Environment
The core pattern:
- Start a test
WorkflowEnvironmentwithstart_local - Create a Worker in that environment with your Workflows and Activities registered
- Execute the Workflow using the environment's client
- Assert on the result
require 'minitest/autorun'
require 'securerandom'
require 'temporalio/testing/workflow_environment'
require 'temporalio/worker'
require_relative '../workflows/my_workflow'
require_relative '../activities/my_activity'
class MyWorkflowTest < Minitest::Test
def test_workflow_returns_expected_result
Temporalio::Testing::WorkflowEnvironment.start_local do |env|
task_queue = SecureRandom.uuid
worker = Temporalio::Worker.new(
client: env.client,
task_queue: task_queue,
workflows: [MyWorkflow],
activities: [MyActivity]
)
worker.run do
result = env.client.execute_workflow(
MyWorkflow,
'input-arg',
id: SecureRandom.uuid,
task_queue: task_queue
)
assert_equal 'expected output', result
end
end
end
end
For workflows with long durations (timers, sleeps), use start_time_skipping instead of start_local:
Temporalio::Testing::WorkflowEnvironment.start_time_skipping do |env|
# Timers are automatically skipped
end
Mocking Activities
Create fake activity classes with the same activity name as the real ones. Pass them to the Worker instead of the real activities:
class FakeComposeGreetingActivity < Temporalio::Activity::Definition
activity_name 'ComposeGreetingActivity'
def execute(input)
'mocked greeting'
end
end
class MyWorkflowMockTest < Minitest::Test
def test_workflow_with_mocked_activity
Temporalio::Testing::WorkflowEnvironment.start_local do |env|
task_queue = SecureRandom.uuid
worker = Temporalio::Worker.new(
client: env.client,
task_queue: task_queue,
workflows: [MyWorkflow],
activities: [FakeComposeGreetingActivity]
)
worker.run do
result = env.client.execute_workflow(
MyWorkflow,
'test-input',
id: SecureRandom.uuid,
task_queue: task_queue
)
assert_equal 'mocked greeting', result
end
end
end
end
Testing Signals and Queries
Use start_workflow to get a handle, then interact via signal/query methods:
class SignalQueryTest < Minitest::Test
def test_signal_and_query
Temporalio::Testing::WorkflowEnvironment.start_local do |env|
task_queue = SecureRandom.uuid
worker = Temporalio::Worker.new(
client: env.client,
task_queue: task_queue,
workflows: [MyWorkflow],
activities: [MyActivity]
)
worker.run do
handle = env.client.start_workflow(
MyWorkflow,
id: SecureRandom.uuid,
task_queue: task_queue
)
# Send a signal
handle.signal(MyWorkflow.my_signal, 'signal-data')
# Query workflow state
status = handle.query(MyWorkflow.get_status)
assert_equal 'expected-status', status
# Wait for completion
result = handle.result
assert_equal 'done', result
end
end
end
end
Testing Failure Cases
Test workflows that encounter errors using activities that raise exceptions:
class FailingActivity < Temporalio::Activity::Definition
activity_name 'MyActivity'
def execute(input)
raise Temporalio::Error::ApplicationError.new('Simulated failure', non_retryable: true)
end
end
class FailureTest < Minitest::Test
def test_workflow_handles_activity_failure
Temporalio::Testing::WorkflowEnvironment.start_local do |env|
task_queue = SecureRandom.uuid
worker = Temporalio::Worker.new(
client: env.client,
task_queue: task_queue,
workflows: [MyWorkflow],
activities: [FailingActivity]
)
worker.run do
assert_raises(Temporalio::Error::WorkflowFailureError) do
env.client.execute_workflow(
MyWorkflow,
'input',
id: SecureRandom.uuid,
task_queue: task_queue
)
end
end
end
end
end
Workflow Replay Testing
Use WorkflowReplayer to verify that workflow code changes remain compatible with existing histories:
require 'temporalio/worker/workflow_replayer'
require 'temporalio/workflow_history'
class ReplayTest < Minitest::Test
def test_replay_from_json
json = File.read('test/fixtures/my_workflow_history.json')
replayer = Temporalio::Worker::WorkflowReplayer.new(workflows: [MyWorkflow])
# Replay a single workflow history
replayer.replay_workflow(
Temporalio::WorkflowHistory.from_history_json(json)
)
end
def test_replay_bulk
histories = Dir['test/fixtures/histories/*.json'].map do |path|
Temporalio::WorkflowHistory.from_history_json(File.read(path))
end
replayer = Temporalio::Worker::WorkflowReplayer.new(workflows: [MyWorkflow])
# Replay multiple histories - raises on nondeterminism
replayer.replay_workflows(histories)
end
end
Activity Testing
Use ActivityEnvironment to test activities in isolation without a full Temporal server:
require 'temporalio/testing/activity_environment'
class ActivityTest < Minitest::Test
def test_activity_returns_greeting
env = Temporalio::Testing::ActivityEnvironment.new
result = env.run(MyActivity, 'World')
assert_equal 'Hello, World!', result
end
end
Best Practices
- Use
start_localfor most tests - provides a real Temporal environment without external dependencies - Use
start_time_skippingfor timer tests - automatically skips timers rather than waiting - Mock external dependencies - create fake activity classes with
activity_namematching the real activity - Test replay compatibility - add replay tests when changing workflow code to catch nondeterminism errors early
- Use unique IDs per test - use
SecureRandom.uuidfor workflow IDs and task queue names to avoid conflicts - Test signals and queries explicitly - use
start_workflowto get a handle rather thanexecute_workflow