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

activityHandler Func<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

DateTime

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

TaskOrchestrationEntityFeature

InstanceId

Gets the unique ID of the current orchestration instance.

public override string InstanceId { get; }

Property Value

string

IsReplaying

Gets a value indicating whether the orchestrator is currently replaying a previous execution.

public override bool IsReplaying { get; }

Property Value

bool

true if the orchestrator is currently replaying a previous execution; otherwise false.

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

ILoggerFactory

Name

Gets the name of the task orchestration.

public override TaskName Name { get; }

Property Value

TaskName

Parent

Gets the parent instance or null if there is no parent orchestration.

public override ParentOrchestrationInstance? Parent { get; }

Property Value

ParentOrchestrationInstance

Properties

Gets the configuration settings for the orchestration context.

public override IReadOnlyDictionary<string, object?> Properties { get; }

Property Value

IReadOnlyDictionary<string, object>

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

ILoggerFactory

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

string

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

name TaskName

The name of the activity to call.

input object

The serializable input to pass to the activity.

options TaskOptions

Additional 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

TResult

The 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

orchestratorName TaskName

The name of the orchestrator to call.

input object

The serializable input to pass to the sub-orchestrator.

options TaskOptions

Additional 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

TResult

The 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

version string

The 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

options ContinueAsNewOptions

Options 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

newInput object

The JSON-serializable input data to re-initialize the instance with.

preserveUnprocessedEvents bool

If set to true, re-adds any unprocessed external events into the new execution history when the orchestration instance restarts. If false, 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

fireAt DateTime

The time at which the durable timer would fire.

cancellationToken CancellationToken

The 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 T or null if no input was provided.

Type Parameters

T

The 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

Guid

The new Guid value.

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

instanceId string

The ID of the orchestration instance to send the event to.

eventName string

The name of the event to wait for. Event names are case-insensitive.

payload object

The 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

customStatus object

A serializable value to assign as the custom status value or null to 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

eventName string

The 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.

cancellationToken CancellationToken

A CancellationToken to 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

T

Any 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.