Merk
Tilgang til denne siden krever autorisasjon. Du kan prøve å logge på eller endre kataloger.
Tilgang til denne siden krever autorisasjon. Du kan prøve å endre kataloger.
Unit testing durable orchestrations helps you verify business logic and catch errors early. Orchestrations coordinate multiple activities and can grow complex quickly, so tests protect against regressions as your workflow evolves.
Select the tab that matches your project: Durable Functions if you use Azure Functions, or Durable Task SDKs if you use the standalone SDK without Azure Functions.
With Durable Functions, you test orchestrators, activities, and client (trigger) functions by mocking the framework-provided context objects and calling your functions directly. This approach isolates your business logic from the Azure Functions runtime.
Here's a minimal C# orchestrator test to show the pattern:
[Fact]
public async Task MyOrchestrator_CallsActivity()
{
var contextMock = new Mock<TaskOrchestrationContext>();
contextMock.Setup(x => x.CallActivityAsync<string>(
It.IsAny<TaskName>(), It.IsAny<string>(), It.IsAny<TaskOptions>()))
.ReturnsAsync("result");
var result = await MyOrchestrator.Run(contextMock.Object);
Assert.Equal("result", result);
}
The rest of this article covers this pattern in detail for C# and Python.
The standalone Durable Task SDKs provide built-in test infrastructure that runs orchestrations in-memory without external dependencies. You register orchestrators and activities with a test worker, schedule orchestrations through a test client, and assert on the results. No mocking is required for C# and JavaScript. Python uses a generator-based approach with manual result injection.
Here's a minimal C# test to show the pattern:
[Fact]
public async Task MyOrchestrator_Completes()
{
await using var host = await DurableTaskTestHost.StartAsync(tasks =>
{
tasks.AddOrchestrator<MyOrchestrator>();
tasks.AddActivity<MyActivity>();
});
string id = await host.Client.ScheduleNewOrchestrationInstanceAsync(nameof(MyOrchestrator));
var result = await host.Client.WaitForInstanceCompletionAsync(id, getInputsAndOutputs: true);
Assert.Equal(OrchestrationRuntimeStatus.Completed, result.RuntimeStatus);
}
The rest of this article covers this pattern in detail for C#, Python, and JavaScript.
Prerequisites
- xUnit — test framework
- Moq — mocking framework
- Familiarity with the .NET isolated worker model
- xUnit — test framework
- The
Microsoft.DurableTask.InProcessTestHostNuGet package (v1.0.0 or later)
Test orchestrator functions
Orchestrator functions coordinate activities, timers, and external events. They typically contain the most business logic and benefit the most from unit testing.
Mock the orchestration context to control the return values of activity calls. Then call your orchestrator directly and verify the output.
Consider this orchestrator that calls an activity three times:
[Function(nameof(HelloCitiesOrchestration))]
public static async Task<List<string>> HelloCities(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var outputs = new List<string>
{
await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo"),
await context.CallActivityAsync<string>(nameof(SayHello), "Seattle"),
await context.CallActivityAsync<string>(nameof(SayHello), "London")
};
return outputs;
}
Use Moq to mock TaskOrchestrationContext and set up expected return values for each activity call.
Note
The It.Is<TaskName>(...) pattern is required because CallActivityAsync accepts a TaskName struct, not a plain string. Moq needs the explicit type match.
[Fact]
public async Task HelloCities_ReturnsExpectedGreetings()
{
var contextMock = new Mock<TaskOrchestrationContext>();
// Mock each activity call to return a known value
contextMock.Setup(x => x.CallActivityAsync<string>(
It.Is<TaskName>(n => n.Name == nameof(SayHello)),
It.Is<string>(n => n == "Tokyo"),
It.IsAny<TaskOptions>())).ReturnsAsync("Hello Tokyo!");
contextMock.Setup(x => x.CallActivityAsync<string>(
It.Is<TaskName>(n => n.Name == nameof(SayHello)),
It.Is<string>(n => n == "Seattle"),
It.IsAny<TaskOptions>())).ReturnsAsync("Hello Seattle!");
contextMock.Setup(x => x.CallActivityAsync<string>(
It.Is<TaskName>(n => n.Name == nameof(SayHello)),
It.Is<string>(n => n == "London"),
It.IsAny<TaskOptions>())).ReturnsAsync("Hello London!");
var result = await HelloCitiesOrchestration.HelloCities(contextMock.Object);
Assert.Equal(3, result.Count);
Assert.Equal("Hello Tokyo!", result[0]);
Assert.Equal("Hello Seattle!", result[1]);
Assert.Equal("Hello London!", result[2]);
}
Use DurableTaskTestHost to run orchestrations in-memory. Register your production orchestrator and activity classes, schedule an orchestration, and assert on the result.
Given these production classes:
class HelloCitiesOrchestrator : TaskOrchestrator<string, List<string>>
{
public override async Task<List<string>> RunAsync(
TaskOrchestrationContext context, string input)
{
var outputs = new List<string>
{
await context.CallActivityAsync<string>(nameof(SayHelloActivity), "Tokyo"),
await context.CallActivityAsync<string>(nameof(SayHelloActivity), "Seattle"),
await context.CallActivityAsync<string>(nameof(SayHelloActivity), "London")
};
return outputs;
}
}
class SayHelloActivity : TaskActivity<string, string>
{
public override Task<string> RunAsync(TaskActivityContext context, string name)
{
return Task.FromResult($"Hello {name}!");
}
}
Register them directly in the test host:
[Fact]
public async Task HelloCities_ReturnsExpectedGreetings()
{
await using var host = await DurableTaskTestHost.StartAsync(tasks =>
{
tasks.AddOrchestrator<HelloCitiesOrchestrator>();
tasks.AddActivity<SayHelloActivity>();
});
string instanceId = await host.Client.ScheduleNewOrchestrationInstanceAsync(
nameof(HelloCitiesOrchestrator));
OrchestrationMetadata result = await host.Client.WaitForInstanceCompletionAsync(
instanceId, getInputsAndOutputs: true);
Assert.Equal(OrchestrationRuntimeStatus.Completed, result.RuntimeStatus);
var output = result.ReadOutputAs<List<string>>();
Assert.Equal(3, output.Count);
Assert.Equal("Hello Tokyo!", output[0]);
Assert.Equal("Hello Seattle!", output[1]);
Assert.Equal("Hello London!", output[2]);
}
DurableTaskTestHost runs a complete in-memory orchestration engine. No external services or sidecar processes are required.
Test activity functions
Activity functions contain the actual work — calling APIs, processing data, or interacting with external systems. They're the simplest function type to test because they have no framework-specific replay behavior.
Activity functions in Azure Functions receive an input and optionally a FunctionContext. Test them like any other function:
[Function(nameof(SayHello))]
public static string SayHello(
[ActivityTrigger] string name, FunctionContext executionContext)
{
return $"Hello {name}!";
}
[Fact]
public void SayHello_ReturnsExpectedGreeting()
{
var result = HelloCitiesOrchestration.SayHello("Tokyo", Mock.Of<FunctionContext>());
Assert.Equal("Hello Tokyo!", result);
}
Activity functions receive a context object and an input. The context provides metadata like the orchestration ID and task ID, but most tests don't need it.
Using the SayHelloActivity class from the orchestrator example, call RunAsync directly with a mock context:
[Fact]
public async Task SayHello_ReturnsExpectedGreeting()
{
var activity = new SayHelloActivity();
var contextMock = new Mock<TaskActivityContext>();
var result = await activity.RunAsync(contextMock.Object, "Tokyo");
Assert.Equal("Hello Tokyo!", result);
}
When you use DurableTaskTestHost, activities also run as part of the orchestration test. You don't need separate activity tests unless the activity has complex logic.
Test client functions
Client functions (also called trigger functions) start orchestrations and manage instances. They use the durable client binding to interact with the orchestration engine.
Consider this HTTP trigger that starts an orchestration:
[Function("HelloCitiesOrchestration_HttpStart")]
public static async Task<HttpResponseData> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext)
{
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
nameof(HelloCitiesOrchestration));
return await client.CreateCheckStatusResponseAsync(req, instanceId);
}
Mock DurableTaskClient to return a known instance ID:
[Fact]
public async Task HttpStart_ReturnsAccepted()
{
var durableClientMock = new Mock<DurableTaskClient>("testClient");
var functionContextMock = new Mock<FunctionContext>();
var instanceId = "test-instance-id";
durableClientMock
.Setup(x => x.ScheduleNewOrchestrationInstanceAsync(
It.IsAny<TaskName>(),
It.IsAny<object>(),
It.IsAny<StartOrchestrationOptions>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(instanceId);
var mockRequest = CreateMockHttpRequest(functionContextMock.Object);
var responseMock = new Mock<HttpResponseData>(functionContextMock.Object);
responseMock.SetupGet(r => r.StatusCode).Returns(HttpStatusCode.Accepted);
durableClientMock
.Setup(x => x.CreateCheckStatusResponseAsync(
It.IsAny<HttpRequestData>(),
It.IsAny<string>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(responseMock.Object);
var result = await HelloCitiesOrchestration.HttpStart(
mockRequest, durableClientMock.Object, functionContextMock.Object);
Assert.Equal(HttpStatusCode.Accepted, result.StatusCode);
}
Test client operations
With the standalone Durable Task SDKs, client operations (scheduling orchestrations, querying status, raising events) use the same TestOrchestrationClient already shown in the orchestrator tests. No separate client function exists — you call the client API directly.
DurableTaskTestHost exposes host.Client, which is a fully functional DurableTaskClient. Use it to test client-level operations like scheduling, querying, or terminating orchestrations:
[Fact]
public async Task Client_CanQueryOrchestrationStatus()
{
await using var host = await DurableTaskTestHost.StartAsync(tasks =>
{
tasks.AddOrchestrator<HelloCitiesOrchestrator>();
tasks.AddActivity<SayHelloActivity>();
});
string instanceId = await host.Client.ScheduleNewOrchestrationInstanceAsync(
nameof(HelloCitiesOrchestrator));
// Query status while the orchestration runs
OrchestrationMetadata metadata = await host.Client.WaitForInstanceCompletionAsync(
instanceId, getInputsAndOutputs: true);
Assert.Equal(OrchestrationRuntimeStatus.Completed, metadata.RuntimeStatus);
Assert.Equal(instanceId, metadata.InstanceId);
}