Test executor for Temporalex workflows.
Implements the same GenServer.call protocol as the production executor,
but operates in step-by-step mode. Workflow code can't tell the difference.
Usage
test "checkout charges then sends receipt" do
{:ok, exec} = Temporalex.Testing.start_workflow(MyWorkflow, %{"id" => "123"})
assert {:activity, call} = Temporalex.Testing.next(exec)
assert call.type == "MyApp.Activities.Payment.charge"
assert {:activity, call} = Temporalex.Testing.resolve(exec, {:ok, "charge_123"})
assert call.type == "MyApp.Activities.Email.send_receipt"
assert {:ok, result} = Temporalex.Testing.resolve(exec, {:ok, :sent})
assert result == %{charge_id: "charge_123"}
end
Summary
Functions
Set the workflow's cancelled flag.
Mark a patch id as "seen in history" — mirrors what a notify_has_patch
job would do during replay. After this, patched?(id) returns true.
Returns what the workflow is currently blocked on.
Query the workflow's published state.
Provide a result for the current blocking call and advance to the next one.
Returns the same values as next/1.
Run an activity function directly for testing.
Run a workflow with a pre-loaded operation log. Convenience wrapper that feeds results automatically and returns the final workflow result.
Send a signal into the workflow. Works both inside and outside receive.
Send an update into the workflow. Only works inside receive with a matching handler.
Flip the executor into replay mode. Subsequent patched? calls that
haven't seen the patch id (via mark_patch_seen/2) will return false.
Start a workflow in test mode. Returns {:ok, executor_pid}.
Functions
Set the workflow's cancelled flag.
Mark a patch id as "seen in history" — mirrors what a notify_has_patch
job would do during replay. After this, patched?(id) returns true.
Returns what the workflow is currently blocked on.
Possible returns:
{:activity, %{type, input, opts}}— waiting for an activity result{:sleep, duration_ms}— waiting for a timer{:signal, name}— waiting for a signal (viawait_for_signal){:side_effect, fun}— waiting for side effect resolution{:receive, %{signals: [...], updates: [...], timeout: ...}}— in a receive loop{:ok, result}— workflow completed successfully{:error, reason}— workflow failed{:continue_as_new, args}— workflow wants to restart
Query the workflow's published state.
Provide a result for the current blocking call and advance to the next one.
Returns the same values as next/1.
Run an activity function directly for testing.
Example
{:ok, result} = Temporalex.Testing.run_activity(MyActivities, :charge, [100])
Run a workflow with a pre-loaded operation log. Convenience wrapper that feeds results automatically and returns the final workflow result.
Example
{:ok, result} = Temporalex.Testing.run_workflow(MyWorkflow, %{"id" => "123"},
log: [
{:activity, "MyApp.Activities.Payment.charge", {:ok, "charge_123"}},
{:activity, "MyApp.Activities.Email.send_receipt", {:ok, :sent}}
]
)
Send a signal into the workflow. Works both inside and outside receive.
Send an update into the workflow. Only works inside receive with a matching handler.
Returns the handler's reply value, or {:error, reason} if validation fails.
Flip the executor into replay mode. Subsequent patched? calls that
haven't seen the patch id (via mark_patch_seen/2) will return false.
Start a workflow in test mode. Returns {:ok, executor_pid}.
Options:
:is_replaying— when true,patched?/1returns false for patches that haven't been marked as seen (via:seen_patchesormark_patch_seen/2). Models a Temporal replay activation.:seen_patches— a list of patch ids to pre-populate as "seen", mirroringnotify_has_patchjobs from an activation's history.