Test Durable activity dispatch
TestOrchestrationContext supports a focused unit-test boundary for .NET isolated Durable
orchestrators: it records and dispatches CallActivityAsync calls. Use it when orchestration logic
primarily chooses an activity name, input, options, and expected result type.
Dispatch to a real activity
Provide an async handler that maps recorded calls to real activity instances or explicit test results:
[Fact]
public async Task Orchestrator_dispatches_to_real_activity_and_records_call()
{
var activity = new GreetingActivity("Hello");
var context = new TestOrchestrationContext(async call =>
{
Assert.Equal(nameof(GreetingActivity), call.ActivityName);
Assert.Equal(typeof(string), call.ResultType);
return await activity.RunAsync(Assert.IsType<string>(call.Input));
});
var result = await GreetingOrchestrator.RunAsync(context);
Assert.Equal("Hello, Ada!", result);
var call = Assert.Single(context.ActivityCalls);
Assert.Equal(nameof(GreetingActivity), call.ActivityName);
Assert.Equal("Ada", call.Input);
Assert.Null(call.Options);
}
ActivityCalls preserves scheduling order and exposes the task name, input, result type, and
options. The handler must return a value compatible with the generic result type; null for a
non-nullable value type and incompatible results fail with InvalidOperationException.
Supported and unsupported behavior
Only CallActivityAsync is emulated. Runtime-dependent context members fail explicitly:
[Fact]
public void Runtime_dependent_members_are_not_supported()
{
var context = new TestOrchestrationContext(_ => Task.FromResult<object?>(null));
Assert.Throws<NotSupportedException>(() => _ = context.InstanceId);
Assert.Throws<NotSupportedException>(() => context.GetInput<string>());
Assert.Throws<NotSupportedException>(() => context.NewGuid());
}
The test context does not emulate:
- Durable history or deterministic replay.
- Timers or orchestration time.
- External events.
- Sub-orchestrators or child-instance lifecycle.
- Entity calls, suspension, termination, purge, or instance management.
- Runtime-generated instance IDs or deterministic GUIDs.
Those behaviors require the real Durable runtime or a dedicated runtime-level harness. Do not use this helper to claim replay safety or production scheduling correctness.
Troubleshooting dispatch
- An unexpected activity should make the handler throw with the recorded activity name.
- A null result for a value type or a mismatched result type produces an explicit dispatch error.
- If orchestrator code reads runtime-dependent members,
NotSupportedExceptionidentifies that the scenario has crossed this helper's boundary.
See DurableFunctionTests for
handler validation and result-type failure coverage.
See the Azure Functions API reference for
TestOrchestrationContext and RecordedActivityCall.