Class TestOrchestrationContext
- Namespace
- XBullet.EasyTesting.AzureFunctions
- Assembly
- XBullet.EasyTesting.AzureFunctions.dll
An in-memory orchestration context that records activity calls and dispatches them to test code.
public sealed class TestOrchestrationContext : TaskOrchestrationContext
- Inheritance
-
TestOrchestrationContext
- Inherited Members
Remarks
Only activity invocation is emulated. Members that require Durable Task runtime state throw NotSupportedException.
Constructors
TestOrchestrationContext(Func<RecordedActivityCall, Task<object?>>)
Initializes a context that dispatches activity calls through activityHandler.
public TestOrchestrationContext(Func<RecordedActivityCall, Task<object?>> activityHandler)
Parameters
activityHandlerFunc<RecordedActivityCall, Task<object>>A non-null asynchronous callback retained by the context and invoked once for every scheduled activity after the call is recorded. It must return a value assignable to the requested result type.
Properties
ActivityCalls
Gets activity calls in scheduling order.
public IReadOnlyList<RecordedActivityCall> ActivityCalls { get; }
Property Value
- IReadOnlyList<RecordedActivityCall>
A live read-only view in scheduling order. Calls retain their input and options without cloning.
ActivityHandler
Gets or sets the delegate used to execute scheduled activities.
public Func<RecordedActivityCall, Task<object?>> ActivityHandler { get; set; }
Property Value
- Func<RecordedActivityCall, Task<object>>
The non-null callback used for future calls. Assigning a new callback does not affect calls already recorded or awaiting a previous handler.
CurrentUtcDateTime
Gets the current orchestration time in UTC.
public override DateTime CurrentUtcDateTime { get; }
Property Value
Remarks
The current orchestration time is stored in the orchestration history and this API will return the same value each time it is called from a particular point in the orchestration's execution. It is a deterministic, replay-safe replacement for existing .NET APIs for getting the current time, such as UtcNow and UtcNow.
Entities
Gets the entity feature, for interacting with entities.
public override TaskOrchestrationEntityFeature Entities { get; }
Property Value
InstanceId
Gets the unique ID of the current orchestration instance.
public override string InstanceId { get; }
Property Value
IsReplaying
Gets a value indicating whether the orchestrator is currently replaying a previous execution.
public override bool IsReplaying { get; }
Property Value
- bool
trueif the orchestrator is currently replaying a previous execution; otherwisefalse.
Remarks
Orchestrator functions are "replayed" after being unloaded from memory to reconstruct local variable state.
During a replay, previously executed tasks will be completed automatically with previously seen values
that are stored in the orchestration history. One the orchestrator reaches the point in the orchestrator
where it's no longer replaying existing history, the IsReplaying property will return false.
You can use this property if you have logic that needs to run only when not replaying. For example,
certain types of application logging may become too noisy when duplicated as part of replay. The
application code could check to see whether the function is being replayed and then issue the log statements
when this value is false.
LoggerFactory
Gets the logger factory for this context.
protected override ILoggerFactory LoggerFactory { get; }
Property Value
Name
Gets the name of the task orchestration.
public override TaskName Name { get; }
Property Value
Parent
Gets the parent instance or null if there is no parent orchestration.
public override ParentOrchestrationInstance? Parent { get; }
Property Value
Properties
Gets the configuration settings for the orchestration context.
public override IReadOnlyDictionary<string, object?> Properties { get; }
Property Value
ReplaySafeLoggerFactory
Gets an ILoggerFactory whose loggers are replay-safe, meaning they suppress log output during orchestration replay. This is the recommended way to expose logger functionality when wrapping a TaskOrchestrationContext instance.
public override ILoggerFactory ReplaySafeLoggerFactory { get; }
Property Value
Remarks
Loggers created by this factory automatically check IsReplaying and suppress duplicate log messages during replay. This is equivalent to calling CreateReplaySafeLogger(string) for each logger category.
Context wrapper implementations can delegate LoggerFactory to this property
on the inner context: protected override ILoggerFactory LoggerFactory => inner.ReplaySafeLoggerFactory;.
Version
Gets the version of the current orchestration instance, which was set when the instance was created.
public override string Version { get; }
Property Value
Methods
CallActivityAsync<TResult>(TaskName, object?, TaskOptions?)
Asynchronously invokes an activity by name and with the specified input value.
public override Task<TResult> CallActivityAsync<TResult>(TaskName name, object? input = null, TaskOptions? options = null)
Parameters
nameTaskNameThe name of the activity to call.
inputobjectThe serializable input to pass to the activity.
optionsTaskOptionsAdditional options that control the execution and processing of the activity.
Returns
- Task<TResult>
A task that completes when the activity completes or fails. The result of the task is the activity's return value.
Type Parameters
TResultThe type into which to deserialize the activity's output.
Remarks
Activities are the basic unit of work in a durable task orchestration. Unlike orchestrators, which are not allowed to do any I/O or call non-deterministic APIs, activities have no implementation restrictions.
An activity may execute in the local machine or a remote machine. The exact behavior depends on the underlying storage provider, which is responsible for distributing tasks across machines. In general, you should never make any assumptions about where an activity will run. You should also assume at-least-once execution guarantees for activities, meaning that an activity may be executed twice if, for example, there is a process failure before the activities result is saved into storage.
Both the inputs and outputs of activities are serialized and stored in durable storage. It's highly recommended to not include any sensitive data in activity inputs or outputs. It's also recommended to not use large payloads for activity inputs and outputs, which can result in expensive serialization and network utilization. For data that cannot be cheaply or safely persisted to storage, it's recommended to instead pass references (for example, a URL to a storage blob) to the data and have activities fetch the data directly as part of their implementation.
Exceptions
- ArgumentException
The specified activity does not exist.
- InvalidOperationException
Thrown if the calling thread is anything other than the main orchestrator thread.
- TaskFailedException
The activity failed with an unhandled exception. The details of the failure can be found in the FailureDetails property.
CallSubOrchestratorAsync<TResult>(TaskName, object?, TaskOptions?)
Executes a named sub-orchestrator and returns the result.
public override Task<TResult> CallSubOrchestratorAsync<TResult>(TaskName orchestratorName, object? input = null, TaskOptions? options = null)
Parameters
orchestratorNameTaskNameThe name of the orchestrator to call.
inputobjectThe serializable input to pass to the sub-orchestrator.
optionsTaskOptionsAdditional options that control the execution and processing of the sub-orchestrator. Callers can choose to supply the derived type SubOrchestrationOptions.
Returns
- Task<TResult>
A task that completes when the sub-orchestrator completes or fails.
Type Parameters
TResultThe type into which to deserialize the sub-orchestrator's output.
Remarks
In addition to activities, orchestrators can schedule other orchestrators, creating sub-orchestrations. A sub-orchestration has its own instance ID, history, and status that is independent of the parent orchestrator that started it.
Sub-orchestrations have many benefits:
- You can split large orchestrations into a series of smaller sub-orchestrations, making your code more maintainable.
- You can distribute orchestration logic across multiple compute nodes concurrently, which is useful if your orchestration logic otherwise needs to coordinate a lot of tasks.
- You can reduce memory usage and CPU overhead by keeping the history of parent orchestrations smaller.
The return value of a sub-orchestration is its output. If a sub-orchestration fails with an exception, then that exception will be surfaced to the parent orchestration, just like it is when an activity task fails with an exception. Sub-orchestrations also support automatic retry policies.
Because sub-orchestrations are independent of their parents, terminating a parent orchestration does not affect any sub-orchestrations. You must terminate each sub-orchestration independently using its instance ID, which is specified by supplying SubOrchestrationOptions in place of TaskOptions.
Exceptions
- ArgumentException
The specified orchestrator does not exist.
- InvalidOperationException
Thrown if the calling thread is anything other than the main orchestrator thread.
- TaskFailedException
The sub-orchestration failed with an unhandled exception. The details of the failure can be found in the FailureDetails property.
CompareVersionTo(string)
Checks if the current orchestration version is greater than the specified version.
public override int CompareVersionTo(string version)
Parameters
versionstringThe version to check against.
Returns
- int
True if the orchestration's version is greater than the provided version, false otherwise.
Remarks
If both versions are empty, this is considered false as neither can be greater.
An empty context version is less than a defined version in the parameter.
An empty parameter version is less than a defined version in the context.
ContinueAsNew(ContinueAsNewOptions)
Restarts the orchestration with the specified options, clearing the history.
public override void ContinueAsNew(ContinueAsNewOptions options)
Parameters
optionsContinueAsNewOptionsOptions for the continue-as-new operation.
Remarks
This overload accepts ContinueAsNewOptions to control the restart behavior, including the new input, whether to preserve unprocessed events, and an optional new version. When NewVersion is set, the framework uses the new version to route the restarted instance to the appropriate orchestrator implementation, enabling version-based dispatch.
The default implementation delegates to
ContinueAsNew(object, bool) using the input and preserve-events values
from options. Subclasses that support version-based
dispatch should override this method.
Orchestrator implementations should complete immediately after calling this method.
ContinueAsNew(object?, bool)
Restarts the orchestration with a new input and clears its history.
public override void ContinueAsNew(object? newInput = null, bool preserveUnprocessedEvents = true)
Parameters
newInputobjectThe JSON-serializable input data to re-initialize the instance with.
preserveUnprocessedEventsboolIf set to
true, re-adds any unprocessed external events into the new execution history when the orchestration instance restarts. Iffalse, any unprocessed external events will be discarded when the orchestration instance restarts.
Remarks
This method is primarily designed for eternal orchestrations, which are orchestrations that may not ever complete. It works by restarting the orchestration, providing it with a new input, and truncating the existing orchestration history. It allows an orchestration to continue running indefinitely without having its history grow unbounded. The benefits of periodically truncating history include decreased memory usage, decreased storage volumes, and shorter orchestrator replays when rebuilding state.
The results of any incomplete tasks will be discarded when an orchestrator calls
ContinueAsNew(object, bool). For example, if a timer is scheduled and then ContinueAsNew(object, bool)
is called before the timer fires, the timer event will be discarded. The only exception to this
is external events. By default, if an external event is received by an orchestration but not yet
processed, the event is saved in the orchestration state until it is received by a call to
WaitForExternalEvent<T>(string, CancellationToken). These events will continue to remain in memory
even after an orchestrator restarts using ContinueAsNew(object, bool). You can disable this behavior and
remove any saved external events by specifying false for the preserveUnprocessedEvents
parameter value.
Orchestrator implementations should complete immediately after calling the ContinueAsNew(object, bool) method.
CreateTimer(DateTime, CancellationToken)
Reports that durable timer scheduling is outside the supported in-process test boundary.
public override Task CreateTimer(DateTime fireAt, CancellationToken cancellationToken)
Parameters
fireAtDateTimeThe time at which the durable timer would fire.
cancellationTokenCancellationTokenThe token that would cancel the timer wait.
Returns
- Task
This method does not return a task because the operation is unsupported.
Exceptions
- InvalidOperationException
Always thrown because durable timer scheduling requires the Durable Functions runtime.
GetInput<T>()
Gets the deserialized input of the orchestrator.
public override T GetInput<T>()
Returns
- T
Returns the deserialized input as an object of type
Tornullif no input was provided.
Type Parameters
TThe expected type of the orchestrator input.
NewGuid()
Creates a new GUID that is safe for replay within an orchestration or operation.
public override Guid NewGuid()
Returns
Remarks
The default implementation of this method creates a name-based UUID V5 using the algorithm from RFC 4122 ยง4.3. The name input used to generate this value is a combination of the orchestration instance ID, the current time, and an internally managed sequence number.
SendEvent(string, string, object)
Raises an external event for the specified orchestration instance.
public override void SendEvent(string instanceId, string eventName, object payload)
Parameters
instanceIdstringThe ID of the orchestration instance to send the event to.
eventNamestringThe name of the event to wait for. Event names are case-insensitive.
payloadobjectThe serializable payload of the external event.
Remarks
The target orchestration can handle the sent event using the WaitForExternalEvent<T>(string, CancellationToken) method.
If the target orchestration doesn't exist, the event will be silently dropped.
SetCustomStatus(object?)
Assigns a custom status value to the current orchestration.
public override void SetCustomStatus(object? customStatus)
Parameters
customStatusobjectA serializable value to assign as the custom status value or
nullto clear the custom status.
Remarks
The customStatus value is serialized and stored in orchestration state and will
be made available to the orchestration status query APIs. The serialized value must not exceed
16 KB of UTF-16 encoded text.
Exceptions
- InvalidOperationException
Thrown if the calling thread is anything other than the main orchestrator thread.
WaitForExternalEvent<T>(string, CancellationToken)
Waits for an event to be raised with name eventName and returns the event data.
public override Task<T> WaitForExternalEvent<T>(string eventName, CancellationToken cancellationToken = default)
Parameters
eventNamestringThe name of the event to wait for. Event names are case-insensitive. External event names can be reused any number of times; they are not required to be unique.
cancellationTokenCancellationTokenA
CancellationTokento use to abort waiting for the event.
Returns
- Task<T>
A task that completes when the external event is received. The value of the task is the deserialized event payload.
Type Parameters
TAny serializable type that represents the event payload.
Remarks
External clients can raise events to a waiting orchestration instance. Similarly, orchestrations can raise events to other orchestrations using the SendEvent(string, string, object) method.
If the current orchestrator instance is not yet waiting for an event named eventName,
then the event will be saved in the orchestration instance state and dispatched immediately when this method is
called. This event saving occurs even if the current orchestrator cancels the wait operation before the event is
received.
Orchestrators can wait for the same event name multiple times, so waiting for multiple events with the same name is allowed. Each external event received by an orchestrator will complete just one task returned by this method.
Exceptions
- InvalidOperationException
Thrown if the calling thread is anything other than the main orchestrator thread.