DurableTask.Testing 1.0.0

dotnet add package DurableTask.Testing --version 1.0.0
                    
NuGet\Install-Package DurableTask.Testing -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DurableTask.Testing" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DurableTask.Testing" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="DurableTask.Testing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DurableTask.Testing --version 1.0.0
                    
#r "nuget: DurableTask.Testing, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DurableTask.Testing@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DurableTask.Testing&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=DurableTask.Testing&version=1.0.0
                    
Install as a Cake Tool

DurableTask.Testing

A fake task client for testing Azure Durable Functions without requiring the actual Azure Functions runtime or Durable Task infrastructure.

License

MIT License

Copyright (c) 2025 DurableTask.Testing Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Overview

DurableTask.Testing provides a FakeDurableTaskClient that implements the DurableTaskClient interface from Microsoft.DurableTask.Client. This allows you to write unit tests for your Durable Functions orchestrations and activities without spinning up the full Azure Functions host.

Key Features

  • Test orchestrations in-process - No Azure Functions host required
  • Override timers - Speed up tests by replacing long delays with short ones
  • Pre-configure external events - Test manual intervention patterns without waiting
  • Mock durable entities - Control entity state in tests
  • Full activity execution - Your actual activity code runs with real dependencies
  • Sub-orchestration support - Test nested orchestrations
  • Wait for completion - Query orchestration status and results

Preconditions

Before using FakeDurableTaskClient, you need to set up a few things:

1. Configure Dependency Injection

Your functions and their dependencies must be registered in a ServiceProvider. The fake client uses this service provider to instantiate your orchestrators and activities.

var services = new ServiceCollection();

// Register your orchestrators
services.AddSingleton<MyOrchestrator>();
services.AddSingleton<AnotherOrchestrator>();

// Register your activities
services.AddSingleton<MyActivities>();
services.AddSingleton<AnotherActivity>();

// Register any dependencies your functions use
services.AddSingleton<ILoggerFactory>(new LoggerFactory());
services.AddSingleton<ISomeDependency, SomeDependency>();

var serviceProvider = services.BuildServiceProvider();

2. Provide the Functions Assembly

The fake client needs to know which assembly contains your functions so it can find them by name.

var functionAssembly = typeof(MyOrchestrator).Assembly;

3. Create a Mock FunctionContext

The FunctionContext is required by the Azure Functions framework. You can mock it using Moq:

var mockFunctionContext = new Mock<FunctionContext>();
mockFunctionContext.Setup(x => x.InvocationId).Returns(Guid.NewGuid().ToString());
mockFunctionContext.Setup(x => x.InstanceServices).Returns(serviceProvider);

4. Mock External Dependencies

The fake client will execute your activity code with real dependencies. You should mock services that cross boundaries:

  • External APIs (HTTP clients, SDK clients)
  • Database connections
  • Storage services (blobs, queues, tables)
  • Third-party services
// Example: Mock an HTTP client
var mockHttpClient = new Mock<IHttpClient>();
mockHttpClient.Setup(x => x.GetAsync(It.IsAny<string>()))
    .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK));

services.AddSingleton(mockHttpClient.Object);

5. Configure Logging (Optional)

For better test debugging, configure logging:

services.AddLogging(builder =>
{
    builder.AddConsole();
    builder.SetMinimumLevel(LogLevel.Debug);
});

Basic Usage

// Arrange
var services = new ServiceCollection();
services.AddSingleton<MyOrchestrator>();
services.AddSingleton<MyActivities>();
// ... register other dependencies

var serviceProvider = services.BuildServiceProvider();
var mockFunctionContext = new Mock<FunctionContext>();
mockFunctionContext.Setup(x => x.InstanceServices).Returns(serviceProvider);

var durableClient = new FakeDurableTaskClient(
    serviceProvider,
    typeof(MyOrchestrator).Assembly,
    mockFunctionContext.Object,
    () => DateTime.UtcNow);

// Act
var instanceId = await durableClient.ScheduleNewOrchestrationInstanceAsync(
    TaskName.From("MyOrchestrator"),
    new MyInput { Value = "test" });

var result = await durableClient.WaitForInstanceCompletionAsync(instanceId);

// Assert
result.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);

Feature Documentation

Testing with Timer Override

Use Case: Testing orchestrations with CreateTimer delays

Long-running orchestrations often use timers for delays, retries, or polling. In tests, you don't want to wait for real-time delays.

[Fact]
public async Task MyOrchestrator_WithTimerOverride_CompletesQuickly()
{
    // Arrange
    var durableClient = new FakeDurableTaskClient(
        serviceProvider,
        typeof(MyOrchestrator).Assembly,
        mockFunctionContext.Object,
        () => DateTime.UtcNow)
        .WithOverrideAllTimerTimes(TimeSpan.FromMilliseconds(10));

    // Act
    var instanceId = await durableClient.ScheduleNewOrchestrationInstanceAsync(
        TaskName.From("MyOrchestrator"),
        new MyInput());

    var result = await durableClient.WaitForInstanceCompletionAsync(instanceId);

    // Assert
    result.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);
}

What it does: Every context.CreateTimer() call is replaced with Task.Delay(10ms) instead of the actual delay time.

Common patterns to test:

  • Retry loops with exponential backoff
  • Polling external APIs
  • Timeout scenarios

Testing with External Events

Use Case: Testing orchestrations that wait for human approval or external signals

Your orchestration may use WaitForExternalEvent<T>() to pause and wait for input. You can pre-configure the event payload so the test doesn't block.

[Fact]
public async Task ApprovalOrchestrator_WithPreConfiguredApproval_Succeeds()
{
    // Arrange
    var durableClient = new FakeDurableTaskClient(
        serviceProvider,
        typeof(ApprovalOrchestrator).Assembly,
        mockFunctionContext.Object,
        () => DateTime.UtcNow)
        .WithEventPayload("ApprovalEvent", new ApprovalEvent
        {
            Approved = true,
            Approver = "Test User",
            Reason = "Approved in test"
        });

    // Act
    var instanceId = await durableClient.ScheduleNewOrchestrationInstanceAsync(
        TaskName.From("ApprovalOrchestrator"),
        new ApprovalRequest { RequestId = "123" });

    var result = await durableClient.WaitForInstanceCompletionAsync(instanceId);

    // Assert
    result.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);
}

What it does: When WaitForExternalEvent("ApprovalEvent") is called, it immediately returns the pre-configured payload instead of waiting.

Alternative: Raise events mid-execution

[Fact]
public async Task ApprovalOrchestrator_RaiseEventDuringExecution_Succeeds()
{
    // Arrange
    var durableClient = new FakeDurableTaskClient(
        serviceProvider,
        typeof(ApprovalOrchestrator).Assembly,
        mockFunctionContext.Object,
        () => DateTime.UtcNow);

    // Act - Start orchestration
    var instanceId = await durableClient.ScheduleNewOrchestrationInstanceAsync(
        TaskName.From("ApprovalOrchestrator"),
        new ApprovalRequest { RequestId = "123" });

    // Give it time to reach the WaitForExternalEvent
    await Task.Delay(100);

    // Raise the event externally
    await durableClient.RaiseEventAsync(instanceId, "ApprovalEvent",
        new ApprovalEvent { Approved = true, Approver = "Manual Approver" });

    var result = await durableClient.WaitForInstanceCompletionAsync(instanceId);

    // Assert
    result.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);
}

Testing with Durable Entities

Use Case: Testing orchestrations that interact with durable entities

Your orchestration may use entities for state management, rate limiting, or distributed locking.

[Fact]
public async Task EntityOrchestrator_WithMockEntity_CorrectlyIncrements()
{
    // Arrange
    var mockEntity = new MockCounterEntity
    {
        InitialValue = 100
    };

    var durableClient = new FakeDurableTaskClient(
        serviceProvider,
        typeof(EntityOrchestrator).Assembly,
        mockFunctionContext.Object,
        () => DateTime.UtcNow)
        .WithEntities((
            new EntityInstanceId("CounterEntity", "test-counter"),
            mockEntity));

    // Act
    var instanceId = await durableClient.ScheduleNewOrchestrationInstanceAsync(
        TaskName.From("EntityOrchestrator"),
        new EntityRequest { CounterId = "test-counter" });

    var result = await durableClient.WaitForInstanceCompletionAsync(instanceId);

    // Assert
    result.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);

    // Verify entity state after execution
    var entityState = await durableClient.Entities.GetEntityAsync<CounterState>(
        new EntityInstanceId("CounterEntity", "test-counter"));

    entityState.State.Value.Should().Be(106); // 100 + 1 + 1+2+3+4+5
}

For entities implementing ITestableEntity<T>:

public class CounterEntity : TaskEntity<CounterState>, ITestableEntity<CounterState>
{
    public Task<int> Get() => Task.FromResult(State.Value);

    public CounterState GetState() => State;
}

What it does:

  • WithEntities() registers your mock or real entity with the fake client
  • Entity operations (CallEntityAsync, SignalEntityAsync) work against the provided entity
  • GetEntityAsync<T>() retrieves the entity's state after execution

Mock entity example:

public class MockCounterEntity : ITestableEntity<CounterState>
{
    private int _value = 0;

    public Task Increment(int amount)
    {
        _value += amount;
        return Task.CompletedTask;
    }

    public Task<int> Get() => Task.FromResult(_value);

    public CounterState GetState() => new() { Value = _value };
}

Testing Sub-Orchestrations

Use Case: Testing orchestrations that call other orchestrations

[Fact]
public async Task ParentOrchestrator_WaitForAllInstances_CompletesParentAndChildren()
{
    // Arrange
    var durableClient = new FakeDurableTaskClient(
        serviceProvider,
        typeof(ParentOrchestrator).Assembly,
        mockFunctionContext.Object,
        () => DateTime.UtcNow);

    // Act
    var instanceId = await durableClient.ScheduleNewOrchestrationInstanceAsync(
        TaskName.From("ParentOrchestrator"),
        new ParentRequest { Regions = new[] { "US", "EU", "APAC" } });

    // Wait for parent and all child orchestrations
    var results = await durableClient.WaitForInstancesAndChildren();

    // Assert
    results.Should().HaveCountGreaterThan(1); // Parent + children
    results.All(r => r.RuntimeStatus == OrchestrationRuntimeStatus.Completed)
        .Should().BeTrue();
}

What it does: WaitForInstancesAndChildren() waits for the parent orchestration and all sub-orchestrations spawned during execution to complete.


Testing Error Handling

Use Case: Testing how your orchestration handles activity failures

[Fact]
public async Task MyOrchestrator_WhenActivityFails_HandlesGracefully()
{
    // Arrange
    var durableClient = new FakeDurableTaskClient(
        serviceProvider,
        typeof(MyOrchestrator).Assembly,
        mockFunctionContext.Object,
        () => DateTime.UtcNow);

    // Act
    var instanceId = await durableClient.ScheduleNewOrchestrationInstanceAsync(
        TaskName.From("MyOrchestrator"),
        new MyInput { ShouldFail = true });

    var result = await durableClient.WaitForInstanceCompletionAsync(instanceId);

    // Assert
    result.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Failed);
    result.FailureDetails.Should().NotBeNull();
    result.FailureDetails.ExceptionType.Should().Be("System.InvalidOperationException");
    result.FailureDetails.Message.Should().Contain("Something went wrong");
}

Getting Orchestration Instances

// Get all instance IDs created during the test
var instances = durableClient.GetInstances();

// Wait for a specific instance
var metadata = await durableClient.WaitForInstanceCompletionAsync(instanceId);

// Check runtime status
metadata.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);

// Check failure details if failed
if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Failed)
{
    metadata.FailureDetails.Should().NotBeNull();
    Console.WriteLine(metadata.FailureDetails.Message);
}

Complete Test Example

Here's a complete example showing all the pieces together:

using FluentAssertions;
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

public class MyOrchestratorTests : IAsyncLifetime
{
    private ServiceProvider _serviceProvider = null!;
    private Mock<FunctionContext> _mockFunctionContext = null!;

    public async Task InitializeAsync()
    {
        var services = new ServiceCollection();

        // Configure logging
        services.AddLogging(builder => builder.AddConsole());

        // Register orchestrators
        services.AddSingleton<MyOrchestrator>();
        services.AddSingleton<ChildOrchestrator>();

        // Register activities
        services.AddSingleton<MyActivities>();

        // Register mocked dependencies
        var mockHttpClient = new Mock<IHttpClient>();
        mockHttpClient.Setup(x => x.GetAsync(It.IsAny<string>()))
            .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK));
        services.AddSingleton(mockHttpClient.Object);

        _serviceProvider = services.BuildServiceProvider();

        _mockFunctionContext = new Mock<FunctionContext>();
        _mockFunctionContext.Setup(x => x.InvocationId).Returns(Guid.NewGuid().ToString());
        _mockFunctionContext.Setup(x => x.InstanceServices).Returns(_serviceProvider);
    }

    public Task DisposeAsync()
    {
        _serviceProvider?.Dispose();
        return Task.CompletedTask;
    }

    [Fact]
    public async Task MyOrchestrator_WithValidInput_CompletesSuccessfully()
    {
        // Arrange
        var durableClient = new FakeDurableTaskClient(
            _serviceProvider,
            typeof(MyOrchestrator).Assembly,
            _mockFunctionContext.Object,
            () => DateTime.UtcNow)
            .WithOverrideAllTimerTimes(TimeSpan.FromMilliseconds(10));

        // Act
        var instanceId = await durableClient.ScheduleNewOrchestrationInstanceAsync(
            TaskName.From("MyOrchestrator"),
            new MyInput { Value = "test" });

        var result = await durableClient.WaitForInstanceCompletionAsync(instanceId);

        // Assert
        result.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);
    }

    [Fact]
    public async Task MyOrchestrator_WithExternalEvent_Succeeds()
    {
        // Arrange
        var durableClient = new FakeDurableTaskClient(
            _serviceProvider,
            typeof(MyOrchestrator).Assembly,
            _mockFunctionContext.Object,
            () => DateTime.UtcNow)
            .WithEventPayload("ApprovalEvent", new ApprovalEvent { Approved = true });

        // Act
        var instanceId = await durableClient.ScheduleNewOrchestrationInstanceAsync(
            TaskName.From("MyOrchestrator"),
            new MyInput { NeedsApproval = true });

        var result = await durableClient.WaitForInstanceCompletionAsync(instanceId);

        // Assert
        result.RuntimeStatus.Should().Be(OrchestrationRuntimeStatus.Completed);
    }
}

API Reference

FakeDurableTaskClient

Constructor
public FakeDurableTaskClient(
    IServiceProvider serviceProvider,
    Assembly functionAssembly,
    FunctionContext functionContext,
    Func<DateTime> getCurrentUtcTime)

Parameters:

  • serviceProvider - The DI container containing your orchestrators, activities, and their dependencies
  • functionAssembly - The assembly containing your orchestrator functions (use typeof(MyOrchestrator).Assembly)
  • functionContext - The mocked Azure Functions FunctionContext
  • getCurrentUtcTime - A function that returns the current UTC time (use () => DateTime.UtcNow for real time, or provide a fixed value for deterministic tests)
Configuration Methods
Method Description
WithOverrideAllTimerTimes(TimeSpan?) Replaces all CreateTimer delays with the specified duration
WithEventPayload(string eventName, object? payload) Pre-configures an external event response
WithEntities(params (EntityInstanceId, ITaskEntity)[]) Registers entities for use in orchestrations
Execution Methods
Method Description
ScheduleNewOrchestrationInstanceAsync(...) Starts a new orchestration instance
WaitForInstanceCompletionAsync(string instanceId) Waits for a single instance to complete
WaitForInstancesAndChildren() Waits for parent and all sub-orchestrations
RaiseEventAsync(string instanceId, string eventName, object? payload) Sends an external event to a running orchestration
GetInstances() Returns all instance IDs created during the test

Limitations

  • No actual timer accuracy: Timers are replaced with Task.Delay, so they're not wall-clock accurate
  • No replay behavior: The fake doesn't simulate the deterministic replay that real Durable Functions performs
  • In-memory only: All state is kept in memory and lost after the test
  • No cross-instance communication: Some cross-instance patterns may not work exactly as in production

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 270 2/17/2026

Initial version