AzureFunctions.TestFramework.Core 0.6.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package AzureFunctions.TestFramework.Core --version 0.6.0
                    
NuGet\Install-Package AzureFunctions.TestFramework.Core -Version 0.6.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="AzureFunctions.TestFramework.Core" Version="0.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AzureFunctions.TestFramework.Core" Version="0.6.0" />
                    
Directory.Packages.props
<PackageReference Include="AzureFunctions.TestFramework.Core" />
                    
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 AzureFunctions.TestFramework.Core --version 0.6.0
                    
#r "nuget: AzureFunctions.TestFramework.Core, 0.6.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 AzureFunctions.TestFramework.Core@0.6.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=AzureFunctions.TestFramework.Core&version=0.6.0
                    
Install as a Cake Addin
#tool nuget:?package=AzureFunctions.TestFramework.Core&version=0.6.0
                    
Install as a Cake Tool

Azure Functions Test Framework

License: MIT NuGet

An integration testing framework for Azure Functions (dotnet-isolated) that provides a TestServer/WebApplicationFactory-like experience.

Project Status: Preview (pre-1.0)

FunctionsTestHost — the single unified test host — is fully functional for the Worker SDK 2.x (.NET 10) samples and test suites. It supports both direct gRPC mode (ConfigureFunctionsWorkerDefaults()) and ASP.NET Core / Kestrel mode (ConfigureFunctionsWebApplication()), and works with both the classic IHostBuilder API and the newer IHostApplicationBuilder / FunctionsApplicationBuilder API introduced in Worker SDK 2.x. No active blockers.

Capabilities

Area Status
HTTP invocation (GET / POST / PUT / PATCH / DELETE / HEAD / OPTIONS) ✅ Both direct gRPC and ASP.NET Core / Kestrel modes
Trigger packages (Timer, Queue, ServiceBus, Blob, EventGrid) ✅ Extension methods + result capture
Durable Functions (starter, orchestrator, activity, sub-orchestrator, external events) ✅ Fake-backed in-process
ASP.NET Core integration (ConfigureFunctionsWebApplication) ✅ Full parameter binding incl. HttpRequest, FunctionContext, typed route params, CancellationToken
WithHostBuilderFactory + ConfigureServices (IHostBuilder) ✅ DI overrides, inherited app services
WithHostApplicationBuilderFactory (FunctionsApplicationBuilder) ✅ Support for the modern FunctionsApplication.CreateBuilder() startup style
Custom route prefixes ✅ Auto-detected from host.json
Middleware testing ✅ End-to-end in both modes
Output binding capture ReadReturnValueAs<T>(), ReadOutputAs<T>(bindingName)
Service access / configuration overrides Services, ConfigureSetting, ConfigureEnvironmentVariable
Metadata discovery IFunctionInvoker.GetFunctions()
NuGet packaging net8.0;net10.0, Source Link, symbol packages, central package management
CI ✅ xUnit + NUnit + TUnit, push + PR

Goals

This framework aims to provide:

  • In-process testing: No func.exe or external processes required
  • Fast execution: Similar performance to ASP.NET Core TestServer
  • Single unified test host: FunctionsTestHost handles both direct gRPC mode and ASP.NET Core / Kestrel mode
  • Full DI control: Override services for testing
  • Middleware support: Test middleware registered in Program.cs

NuGet package map

The shipping package set is currently:

  • AzureFunctions.TestFramework.Core — gRPC-based in-process test host, HTTP client path (both direct gRPC and ASP.NET Core / Kestrel modes), metadata inspection, and shared invocation result types
  • AzureFunctions.TestFramework.Http — currently minimal HTTP-focused package kept in the published package set for future HTTP-specific helpers
  • AzureFunctions.TestFramework.TimerInvokeTimerAsync(...)
  • AzureFunctions.TestFramework.QueueInvokeQueueAsync(...)
  • AzureFunctions.TestFramework.ServiceBusInvokeServiceBusAsync(...)
  • AzureFunctions.TestFramework.BlobInvokeBlobAsync(...)
  • AzureFunctions.TestFramework.EventGridInvokeEventGridAsync(...) for both EventGridEvent and CloudEvent
  • AzureFunctions.TestFramework.Durable — fake-backed durable helpers including ConfigureFakeDurableSupport(...), durable client/provider helpers, status helpers, and direct activity invocation

Project setup requirements

ASP.NET Core shared framework reference

If your function app uses ConfigureFunctionsWebApplication() (i.e., it references Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore), it must declare a framework reference to Microsoft.AspNetCore.App:


<ItemGroup>
  <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

This is the standard requirement for Azure Functions apps that use ASP.NET Core integration. AzureFunctions.TestFramework.Core also declares this framework reference so that ASP.NET Core types are always resolved from the shared runtime in both the function app and the test framework. Without consistent framework resolution, HttpContextConverter cannot read HttpRequest from FunctionContext — the as HttpContext cast silently returns null due to a type identity mismatch between two physical copies of ASP.NET Core assemblies.

ℹ️ You do not need to add FrameworkReference to your test project manually; it is propagated through the test framework's NuGet package metadata.

Common commands

# Build solution
dotnet build --configuration Release

# Run all tests
dotnet test --configuration Release

# 4-flavour test matrix (IHostBuilder / IHostApplicationBuilder × direct gRPC / ASP.NET Core)
dotnet test tests/TestProject.HostBuilder.Tests --no-build --configuration Release
dotnet test tests/TestProject.HostBuilder.AspNetCore.Tests --no-build --configuration Release
dotnet test tests/TestProject.HostApplicationBuilder.Tests --no-build --configuration Release
dotnet test tests/TestProject.HostApplicationBuilder.AspNetCore.Tests --no-build --configuration Release

# Custom route prefix tests (4-flavour)
dotnet test tests/TestProject.CustomRoutePrefix.HostBuilder.Tests --no-build --configuration Release
dotnet test tests/TestProject.CustomRoutePrefix.HostBuilder.AspNetCore.Tests --no-build --configuration Release
dotnet test tests/TestProject.CustomRoutePrefix.HostApplicationBuilder.Tests --no-build --configuration Release
dotnet test tests/TestProject.CustomRoutePrefix.HostApplicationBuilder.AspNetCore.Tests --no-build --configuration Release

# Sample tests (Worker SDK 2.x, Durable, Custom route prefix)
dotnet test samples/Sample.FunctionApp.Worker.Tests --no-build --configuration Release
dotnet test samples/Sample.FunctionApp.Durable.Tests --no-build --configuration Release
dotnet test samples/Sample.FunctionApp.CustomRoutePrefix.Tests --no-build --configuration Release
dotnet test samples/Sample.FunctionApp.CustomRoutePrefix.AspNetCore.Tests --no-build --configuration Release

# Pack NuGet packages locally
dotnet pack --configuration Release --output ./artifacts

Next likely areas

  • Richer durable lifecycle helpers (terminate/suspend/resume and more management helpers)
  • Additional typed helpers for more complex output payloads
  • More middleware scenarios such as authorization and exception handling
  • More binding types such as Event Hubs, Cosmos DB, and SignalR

Approaches

1. FunctionsTestHost — direct gRPC mode

Uses a custom gRPC host that mimics the Azure Functions host, starting the worker in-process and dispatching HTTP requests directly via the gRPC InvocationRequest channel. Works with ConfigureFunctionsWorkerDefaults().

Option A — Inline service registration (override individual services for test doubles):

_testHost = await new FunctionsTestHostBuilder()
    .WithFunctionsAssembly(typeof(MyFunctions).Assembly)
    .ConfigureServices(services =>
    {
        // Register or override dependencies for testing
        services.AddSingleton<IMyService, MockMyService>();
    })
    .BuildAndStartAsync();

Option B — IHostBuilder / Program.CreateWorkerHostBuilder (inherit all app services automatically):

// Program.cs — expose a worker-specific builder for gRPC mode testing
public partial class Program
{
    public static IHostBuilder CreateWorkerHostBuilder(string[] args) =>
        new HostBuilder()
            .ConfigureFunctionsWorkerDefaults()
            .ConfigureServices(ConfigureServices);

    private static void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IMyService, MyService>();
        // ... other registrations
    }
}

// Test — no need to re-register app services
_testHost = await new FunctionsTestHostBuilder()
    .WithFunctionsAssembly(typeof(MyFunctions).Assembly)
    .WithHostBuilderFactory(Program.CreateWorkerHostBuilder)
    .BuildAndStartAsync();

Option C — FunctionsApplicationBuilder / Program.CreateApplicationBuilder (modern FunctionsApplication.CreateBuilder() startup style):

// Program.cs — expose a FunctionsApplicationBuilder factory for direct gRPC mode testing
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting; // required for UseMiddleware<T>()

public partial class Program
{
    public static FunctionsApplicationBuilder CreateApplicationBuilder(string[] args)
    {
        var builder = FunctionsApplication.CreateBuilder(args);
        builder.UseMiddleware<CorrelationMiddleware>();
        builder.Services.AddSingleton<IMyService, MyService>();
        return builder;
    }
}

// Test — framework starts the worker from the FunctionsApplicationBuilder
_testHost = await new FunctionsTestHostBuilder()
    .WithFunctionsAssembly(typeof(MyFunctions).Assembly)
    .WithHostApplicationBuilderFactory(Program.CreateApplicationBuilder)
    .BuildAndStartAsync();

⚠️ UseMiddleware<T>() is an extension method from MiddlewareWorkerApplicationBuilderExtensions in the Microsoft.Extensions.Hosting namespace. Add using Microsoft.Extensions.Hosting; to any file that calls it on a FunctionsApplicationBuilder.

2. FunctionsTestHost — ASP.NET Core / Kestrel mode

The same FunctionsTestHost automatically detects when the worker uses ConfigureFunctionsWebApplication() and routes HttpClient requests to the worker's real Kestrel server instead of dispatching over gRPC. The full ASP.NET Core middleware pipeline runs, and all ASP.NET Core-native binding types (HttpRequest, FunctionContext, Guid route params, CancellationToken) work correctly.

IHostBuilder style:

// Program.cs — expose a host builder for ASP.NET Core / Kestrel mode testing
public partial class Program
{
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        new HostBuilder()
            .ConfigureFunctionsWebApplication()
            .ConfigureServices(ConfigureServices);
}

// Test — framework auto-detects ASP.NET Core mode and routes requests to Kestrel
_testHost = await new FunctionsTestHostBuilder()
    .WithFunctionsAssembly(typeof(MyFunctions).Assembly)
    .WithHostBuilderFactory(Program.CreateHostBuilder)
    .BuildAndStartAsync();

FunctionsApplicationBuilder style:

// Program.cs — expose a web-app builder for ASP.NET Core / Kestrel mode testing
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;

public partial class Program
{
    public static FunctionsApplicationBuilder CreateWebApplicationBuilder(string[] args)
    {
        var builder = FunctionsApplication.CreateBuilder(args);
        builder.ConfigureFunctionsWebApplication(); // enables ASP.NET Core / Kestrel mode — call before UseMiddleware
        builder.UseMiddleware<CorrelationMiddleware>();
        builder.Services.AddSingleton<IMyService, MyService>();
        return builder;
    }
}

// Test
_testHost = await new FunctionsTestHostBuilder()
    .WithFunctionsAssembly(typeof(MyFunctions).Assembly)
    .WithHostApplicationBuilderFactory(Program.CreateWebApplicationBuilder)
    .BuildAndStartAsync();

ℹ️ The framework auto-detects which mode is in use. With ConfigureFunctionsWorkerDefaults(), HTTP requests are dispatched via the gRPC InvocationRequest channel. With ConfigureFunctionsWebApplication(), the framework starts the worker's internal Kestrel server on an ephemeral port and routes HttpClient requests there.

Service overrides in either modeConfigureServices on FunctionsTestHostBuilder lets tests swap out any registered service regardless of which mode is used:

_testHost = await new FunctionsTestHostBuilder()
    .WithFunctionsAssembly(typeof(MyFunctions).Assembly)
    .WithHostBuilderFactory(Program.CreateHostBuilder)   // or WithHostApplicationBuilderFactory(...)
    .ConfigureServices(services =>
    {
        services.RemoveAll<IMyService>();
        services.AddSingleton<IMyService>(new MockMyService());
    })
    .BuildAndStartAsync();

Middleware exampleCorrelationMiddleware reads x-correlation-id, stores it in FunctionContext.Items, and the sample /api/correlation function exposes the value. Tests in both modes assert the middleware end-to-end.

Service access + configuration overridesFunctionsTestHost.Services exposes the worker DI container after startup, and FunctionsTestHostBuilder.ConfigureSetting("Demo:Message", "configured-value") lets tests overlay configuration values that functions can read through IConfiguration.

Optional shared-host pattern — if a test class can safely reset mutable app state between tests, it can amortize worker startup with an IClassFixture. See samples/Sample.FunctionApp.Worker.Tests/SharedFunctionsTestHostFixture.cs and FunctionsTestHostReuseFixtureTests.cs for a concrete example.

3. Timer Trigger Invocation

Use the AzureFunctions.TestFramework.Timer package to invoke timer-triggered functions directly from tests.

// Reference: AzureFunctions.TestFramework.Timer
using AzureFunctions.TestFramework.Timer;
using Microsoft.Azure.Functions.Worker;

public class TimerFunctionTests : IAsyncLifetime
{
    private IFunctionsTestHost _testHost;

    public async Task InitializeAsync()
    {
        _testHost = await new FunctionsTestHostBuilder()
            .WithFunctionsAssembly(typeof(MyTimerFunction).Assembly)
            .BuildAndStartAsync();
    }

    [Fact]
    public async Task HeartbeatTimer_RunsSuccessfully()
    {
        // Invoke with default TimerInfo (IsPastDue = false)
        var result = await _testHost.InvokeTimerAsync("HeartbeatTimer");
        Assert.True(result.Success);
    }

    [Fact]
    public async Task HeartbeatTimer_WhenPastDue_RunsSuccessfully()
    {
        var timerInfo = new TimerInfo { IsPastDue = true };
        var result = await _testHost.InvokeTimerAsync("HeartbeatTimer", timerInfo);
        Assert.True(result.Success);
    }

    public async Task DisposeAsync()
    {
        await _testHost.StopAsync();
        _testHost.Dispose();
    }
}

4. Service Bus Trigger Invocation

Use the AzureFunctions.TestFramework.ServiceBus package to invoke Service Bus–triggered functions directly from tests.

// Reference: AzureFunctions.TestFramework.ServiceBus
using AzureFunctions.TestFramework.ServiceBus;
using Azure.Messaging.ServiceBus;

public class ServiceBusFunctionTests : IAsyncLifetime
{
    private IFunctionsTestHost _testHost;

    public async Task InitializeAsync()
    {
        _testHost = await new FunctionsTestHostBuilder()
            .WithFunctionsAssembly(typeof(MyServiceBusFunction).Assembly)
            .BuildAndStartAsync();
    }

    [Fact]
    public async Task ProcessMessage_WithStringBody_Succeeds()
    {
        var message = new ServiceBusMessage("Hello from test!");
        var result = await _testHost.InvokeServiceBusAsync("ProcessOrderMessage", message);
        Assert.True(result.Success);
    }

    [Fact]
    public async Task ProcessMessage_WithJsonBody_Succeeds()
    {
        var message = new ServiceBusMessage("{\"orderId\": \"abc123\"}")
        {
            ContentType = "application/json",
            MessageId = Guid.NewGuid().ToString()
        };
        var result = await _testHost.InvokeServiceBusAsync("ProcessOrderMessage", message);
        Assert.True(result.Success);
    }

    public async Task DisposeAsync()
    {
        await _testHost.StopAsync();
        _testHost.Dispose();
    }
}

5. Blob Trigger Invocation

Use the AzureFunctions.TestFramework.Blob package to invoke blob-triggered functions directly from tests.

// Reference: AzureFunctions.TestFramework.Blob
using AzureFunctions.TestFramework.Blob;

public class BlobFunctionTests : IAsyncLifetime
{
    private IFunctionsTestHost _testHost;

    public async Task InitializeAsync()
    {
        _testHost = await new FunctionsTestHostBuilder()
            .WithFunctionsAssembly(typeof(MyBlobFunction).Assembly)
            .BuildAndStartAsync();
    }

    [Fact]
    public async Task ProcessBlob_WithTextContent_Succeeds()
    {
        var content = BinaryData.FromString("Hello from blob!");
        var result = await _testHost.InvokeBlobAsync("ProcessBlob", content, blobName: "test/file.txt");
        Assert.True(result.Success);
    }

    [Fact]
    public async Task ProcessBlob_WithJsonContent_Succeeds()
    {
        var content = BinaryData.FromObjectAsJson(new { id = "123", name = "test" });
        var result = await _testHost.InvokeBlobAsync(
            "ProcessBlob", content,
            blobName: "orders/order-123.json",
            containerName: "orders");
        Assert.True(result.Success);
    }

    public async Task DisposeAsync()
    {
        await _testHost.StopAsync();
        _testHost.Dispose();
    }
}

6. Event Grid Trigger Invocation

Use the AzureFunctions.TestFramework.EventGrid package to invoke Event Grid–triggered functions directly from tests. Both EventGridEvent (EventGrid schema) and CloudEvent (CloudEvents schema) are supported.

// Reference: AzureFunctions.TestFramework.EventGrid
using AzureFunctions.TestFramework.EventGrid;
using Azure.Messaging.EventGrid;
using Azure.Messaging;

public class EventGridFunctionTests : IAsyncLifetime
{
    private IFunctionsTestHost _testHost;

    public async Task InitializeAsync()
    {
        _testHost = await new FunctionsTestHostBuilder()
            .WithFunctionsAssembly(typeof(MyEventGridFunction).Assembly)
            .BuildAndStartAsync();
    }

    [Fact]
    public async Task ProcessEvent_WithEventGridEvent_Succeeds()
    {
        var eventGridEvent = new EventGridEvent(
            subject: "orders/order-123",
            eventType: "Order.Created",
            dataVersion: "1.0",
            data: BinaryData.FromObjectAsJson(new { orderId = "123" }));

        var result = await _testHost.InvokeEventGridAsync("ProcessOrderEvent", eventGridEvent);
        Assert.True(result.Success);
    }

    [Fact]
    public async Task ProcessEvent_WithCloudEvent_Succeeds()
    {
        var cloudEvent = new CloudEvent(
            source: "/orders",
            type: "order.created",
            jsonSerializableData: new { orderId = "123" });

        var result = await _testHost.InvokeEventGridAsync("ProcessOrderEvent", cloudEvent);
        Assert.True(result.Success);
    }

    public async Task DisposeAsync()
    {
        await _testHost.StopAsync();
        _testHost.Dispose();
    }
}

7. Durable Functions

The AzureFunctions.TestFramework.Durable package provides fake-backed durable support for in-process integration testing. No real Durable Task runtime is needed.

Why fake-backed? The real Durable Task execution engine relies on external storage (Azure Storage / Netherite / MSSQL) and the Durable Task Framework host, neither of which runs inside the test framework's in-process worker. Instead, ConfigureFakeDurableSupport(...) registers a FakeDurableTaskClient and companion types that intercept [DurableClient] binding resolution at the DI level, letting starter functions, orchestrators, activities, and sub-orchestrators execute fully in-process.

Setup:

_testHost = await new FunctionsTestHostBuilder()
    .WithFunctionsAssembly(typeof(MyDurableFunction).Assembly)
    .ConfigureFakeDurableSupport(provider =>
    {
        // Register orchestration runners
        provider.AddOrchestration<string>("MyOrchestrator", ctx => MyOrchestratorFunction.RunAsync(ctx));
    })
    .BuildAndStartAsync();

Coverage:

  • [DurableClient] DurableTaskClient injection (both gRPC-direct and ASP.NET Core paths)
  • Direct activity invocation via IFunctionsTestHost.InvokeActivityAsync<TResult>(...)
  • Fake orchestration scheduling and activity execution
  • Sub-orchestrator execution via TaskOrchestrationContext.CallSubOrchestratorAsync<TResult>()
  • Custom status via SetCustomStatus(...) and OrchestrationMetadata.ReadCustomStatusAs<T>()
  • External events: both wait-then-raise and buffered raise-before-wait flows
  • FunctionsDurableClientProvider resolution from FunctionsTestHost.Services

Architecture & Design Decisions

This section explains why certain non-obvious implementation choices were made.

Assembly Load Context (ALC) isolation prevention

Problem: The Worker SDK's DefaultMethodInfoLocator.GetMethod() calls AssemblyLoadContext.Default.LoadFromAssemblyPath() during FunctionLoadRequest processing. When the worker runs in-process (same process as the test runner), this can load a second copy of the same assembly, creating two distinct RuntimeTypeHandle values for the same type name. The SDK's built-in converters use context.TargetType == typeof(T) checks that silently fail, leaving trigger parameters (FunctionContext, HttpRequest) null.

Solution (three layers):

  1. Root fix — InProcessMethodInfoLocator: Replaces the SDK's internal IMethodInfoLocator via DispatchProxy (the interface is internal). Searches AppDomain.CurrentDomain.GetAssemblies() for already-loaded assemblies instead of calling LoadFromAssemblyPath. Registered with AddSingleton (not TryAdd) so it wins over the SDK's TryAddSingleton.
  2. Defense-in-depth — TestFunctionContextConverter + TestHttpRequestConverter: Registered at converter index 0 via PostConfigure<WorkerOptions>. These compare types by FullName strings (immune to dual-load) and use reflection to access properties (bypassing is T casts).
  3. Build-time — <FrameworkReference Include="Microsoft.AspNetCore.App" />: Declared in Core csproj so ASP.NET Core types always resolve from the shared runtime, not from NuGet packages. Without this, two physical copies of Microsoft.AspNetCore.Http.Abstractions.dll load and HttpContextConverter.GetHttpContext() returns null.

Function ID resolution

GeneratedFunctionMetadataProvider computes a stable hash for each function (Name + ScriptFile + EntryPoint). GrpcHostService stores the hash-based FunctionId from FunctionMetadataResponse — not the GUID from FunctionLoadRequest — because the worker's internal _functionMap uses the hash. Sending the wrong ID causes "function not found" at invocation time.

GrpcWorker.StopAsync() is a no-op

The Azure Functions worker SDK's GrpcWorker.StopAsync() returns Task.CompletedTask immediately — it does not close the gRPC channel. FunctionsTestHost calls _grpcHostService.SignalShutdownAsync() before stopping _grpcServerManager to gracefully end the EventStream so Kestrel can stop instantly (no 5 s ShutdownTimeout wait).

Durable converter interception

When using ConfigureFunctionsWebApplication(), the ASP.NET Core middleware path does not send synthetic durable binding data in InputData (unlike the gRPC-direct path). The real DurableTaskClientConverter receives null/empty context.Source, returns ConversionResult.Failed(), and [ConverterFallbackBehavior(Disallow)] on DurableClientAttribute blocks fallback. The framework fixes this by registering FakeDurableTaskClientInputConverter in DI as the service for the real DurableTaskClientConverter type, so ActivatorUtilities.GetServiceOrCreateInstance returns our fake converter instead of creating the real one.

IAutoConfigureStartup scanning

The functions assembly contains source-generated classes (FunctionMetadataProviderAutoStartup, FunctionExecutorAutoStartup) implementing IAutoConfigureStartup. WorkerHostService scans for and invokes these to register GeneratedFunctionMetadataProvider and DirectFunctionExecutor, overriding the defaults that would require a functions.metadata file on disk.

Binding cache clearing (FunctionsApplicationBuilder + ASP.NET Core)

Problem: The Worker SDK's IBindingCache<ConversionResult> uses binding-name-only keys (e.g. "req"), not (name, targetType). If user middleware calls GetHttpRequestDataAsync() before FunctionExecutionMiddleware runs, the cache stores a GrpcHttpRequestData under key "req". Later, FunctionExecutionMiddleware calls BindFunctionInputAsync for the same "req" binding with TargetType=HttpRequest — the cache returns the stale GrpcHttpRequestData and the SDK casts (HttpRequest)GrpcHttpRequestDataInvalidCastException.

Solution: In the FunctionsApplicationBuilder path, WorkerHostService appends a cache-clearing middleware after the user factory returns (so it runs after user middleware but before FunctionExecutionMiddleware). The middleware checks context.Items.ContainsKey("HttpRequestContext") — confirming ASP.NET Core mode is active — and uses BindingCacheCleaner.TryClearBindingCache(context.InstanceServices) to clear the internal ConcurrentDictionary via reflection. Converters then re-run with correct state.

ℹ️ In the IHostBuilder path, middleware is registered inside the ConfigureFunctionsWebApplication(builder => { ... }) callback, where the SDK guarantees correct ordering. The framework cannot insert worker middleware from ConfigureServices, but the cache-poisoning scenario is avoided as long as ConfigureFunctionsWebApplication() is called before any UseMiddleware<T>() calls.

Custom route prefix auto-detection

FunctionsTestHostBuilder.Build() reads extensions.http.routePrefix from the functions assembly's host.json. The prefix is used by FunctionsHttpMessageHandler (to strip it when matching routes) and by FunctionsTestHost (to set HttpClient.BaseAddress). This makes custom route prefixes work transparently.

Project Structure

src/
  AzureFunctions.TestFramework.Core/         # gRPC host, worker hosting, HTTP invocation — both modes (net8.0;net10.0)
  AzureFunctions.TestFramework.Http/         # HTTP-specific functionality placeholder (net8.0;net10.0)
  AzureFunctions.TestFramework.Timer/        # TimerTrigger invocation (net8.0;net10.0)
  AzureFunctions.TestFramework.Queue/        # QueueTrigger invocation (net8.0;net10.0)
  AzureFunctions.TestFramework.ServiceBus/   # ServiceBusTrigger invocation (net8.0;net10.0)
  AzureFunctions.TestFramework.Blob/         # BlobTrigger invocation (net8.0;net10.0)
  AzureFunctions.TestFramework.EventGrid/    # EventGridTrigger invocation (net8.0;net10.0)
  AzureFunctions.TestFramework.Durable/      # Fake durable support (net8.0;net10.0)

samples/
  Sample.FunctionApp/                        # Minimal worker app (net10.0) — used by sample test projects
  Sample.FunctionApp.Tests.XUnit/            # xUnit sample test project
  Sample.FunctionApp.Tests.NUnit/            # NUnit sample test project
  Sample.FunctionApp.Tests.TUnit/            # TUnit sample test project
  Sample.FunctionApp.Worker/                 # Worker SDK 2.x (net10.0) — TodoAPI, middleware, triggers
  Sample.FunctionApp.Worker.Tests/           # xUnit — both direct gRPC and ASP.NET Core / Kestrel mode (~7 tests)
  Sample.FunctionApp.Durable/               # Durable Functions sample — HTTP starter + orchestrator + activity
  Sample.FunctionApp.Durable.Tests/          # xUnit — Durable Functions (~25 tests)
  Sample.FunctionApp.CustomRoutePrefix/      # Custom route prefix with ConfigureFunctionsWorkerDefaults()
  Sample.FunctionApp.CustomRoutePrefix.Tests/              # xUnit — custom prefix via direct gRPC (2 tests)
  Sample.FunctionApp.CustomRoutePrefix.AspNetCore/         # Custom route prefix with ConfigureFunctionsWebApplication()
  Sample.FunctionApp.CustomRoutePrefix.AspNetCore.Tests/   # xUnit — custom prefix via ASP.NET Core / Kestrel (3 tests)

tests/
  # 4-flavour test matrix — all share logic from tests/Shared/
  TestProject.HostBuilder/                              # Function app — IHostBuilder, ConfigureFunctionsWorkerDefaults
  TestProject.HostBuilder.Tests/                        # xUnit — direct gRPC, IHostBuilder
  TestProject.HostBuilder.AspNetCore/                   # Function app — IHostBuilder, ConfigureFunctionsWebApplication
  TestProject.HostBuilder.AspNetCore.Tests/             # xUnit — ASP.NET Core / Kestrel, IHostBuilder
  TestProject.HostApplicationBuilder/                   # Function app — FunctionsApplicationBuilder (direct gRPC)
  TestProject.HostApplicationBuilder.Tests/             # xUnit — direct gRPC, FunctionsApplicationBuilder
  TestProject.HostApplicationBuilder.AspNetCore/        # Function app — FunctionsApplicationBuilder + ConfigureFunctionsWebApplication
  TestProject.HostApplicationBuilder.AspNetCore.Tests/  # xUnit — ASP.NET Core / Kestrel, FunctionsApplicationBuilder
  # Custom route prefix 4-flavour matrix (one test project per function app)
  TestProject.CustomRoutePrefix.HostBuilder/            # CRP function app — IHostBuilder, gRPC
  TestProject.CustomRoutePrefix.HostBuilder.Tests/
  TestProject.CustomRoutePrefix.HostBuilder.AspNetCore/ # CRP function app — IHostBuilder, ASP.NET Core
  TestProject.CustomRoutePrefix.HostBuilder.AspNetCore.Tests/
  TestProject.CustomRoutePrefix.HostApplicationBuilder/            # CRP function app — FunctionsApplicationBuilder, gRPC
  TestProject.CustomRoutePrefix.HostApplicationBuilder.Tests/
  TestProject.CustomRoutePrefix.HostApplicationBuilder.AspNetCore/ # CRP function app — FunctionsApplicationBuilder, ASP.NET Core
  TestProject.CustomRoutePrefix.HostApplicationBuilder.AspNetCore.Tests/
  Shared/                                               # Shared test base classes + shared function implementations
  TestProject.Shared/                                   # Shared project consumed by all test projects

Building

dotnet restore
dotnet build

Testing

# All tests
dotnet test

# 4-flavour test matrix
dotnet test tests/TestProject.HostBuilder.Tests
dotnet test tests/TestProject.HostBuilder.AspNetCore.Tests
dotnet test tests/TestProject.HostApplicationBuilder.Tests
dotnet test tests/TestProject.HostApplicationBuilder.AspNetCore.Tests

# Custom route prefix tests (4-flavour)
dotnet test tests/TestProject.CustomRoutePrefix.HostBuilder.Tests
dotnet test tests/TestProject.CustomRoutePrefix.HostBuilder.AspNetCore.Tests
dotnet test tests/TestProject.CustomRoutePrefix.HostApplicationBuilder.Tests
dotnet test tests/TestProject.CustomRoutePrefix.HostApplicationBuilder.AspNetCore.Tests

# Sample tests (Worker SDK 2.x, Durable, Custom route prefix)
dotnet test samples/Sample.FunctionApp.Worker.Tests
dotnet test samples/Sample.FunctionApp.Durable.Tests
dotnet test samples/Sample.FunctionApp.CustomRoutePrefix.Tests
dotnet test samples/Sample.FunctionApp.CustomRoutePrefix.AspNetCore.Tests

# Single test with detailed logging
dotnet test --filter "GetTodos_ReturnsEmptyList" --logger "console;verbosity=detailed"

Known Issues

See KNOWN_ISSUES.md for active caveats.

References

License

MIT

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 is compatible.  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 (10)

Showing the top 5 NuGet packages that depend on AzureFunctions.TestFramework.Core:

Package Downloads
AzureFunctions.TestFramework.Http

HTTP test client support for AzureFunctions.TestFramework

AzureFunctions.TestFramework.Timer

TimerTrigger invocation support for the Azure Functions Test Framework. Enables triggering timer functions in integration tests.

AzureFunctions.TestFramework.Blob

BlobTrigger invocation support for the Azure Functions Test Framework. Enables triggering blob-triggered functions in integration tests.

AzureFunctions.TestFramework.ServiceBus

ServiceBusTrigger invocation support for the Azure Functions Test Framework. Enables triggering Service Bus-triggered functions in integration tests.

AzureFunctions.TestFramework.EventGrid

EventGridTrigger invocation support for the Azure Functions Test Framework. Enables triggering Event Grid-triggered functions in integration tests.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.11.2 180 4/14/2026
0.11.1 188 4/13/2026
0.11.0 203 4/13/2026
0.10.0 222 4/11/2026
0.9.0 199 4/8/2026
0.8.0 180 4/7/2026
0.7.1 187 4/7/2026
0.7.0 181 4/7/2026
0.6.0 199 4/7/2026
0.5.0 189 4/2/2026
0.4.1 207 4/1/2026
0.4.0 199 4/1/2026
0.3.0 202 4/1/2026
0.2.1 207 3/17/2026
0.2.0 199 3/17/2026