You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于.NET 5的Azure Functions如何用xUnit编写覆盖DI与中间件的测试

Azure Functions .NET 5 隔离进程集成测试方案

方案一:内置Host测试(推荐)

该方案不需要启动外部func.exe进程,直接复用项目中的宿主构建逻辑,可完整覆盖依赖注入、中间件、函数执行全链路逻辑,测试速度快且无外部环境依赖

步骤1:重构函数项目的Host构建逻辑

将Program.cs中的Host构建代码提取为公共静态方法,方便测试项目调用:

public static class Program
{
    public static IHost BuildFunctionHost(string[] args)
    {
        return new HostBuilder()
           .ConfigureAppConfiguration(c =>
           {                   
               c.AddCommandLine(args);
               // 可扩展添加测试专属配置源
           })
           .ConfigureFunctionsWorker((c, b) =>
           {
               b.UseMiddleware<FunctionExceptionMiddleware>();
               b.UseFunctionExecutionMiddleware();
           })
           .ConfigureServices(s =>
           {
               // 原有服务注册逻辑,测试时可覆盖替换为Mock服务
               CloudStorageAccount storageAccount = CloudStorageAccount.Parse("");
               BlobServiceClient blobServiceClient = new BlobServiceClient("");

               s.AddScoped(r => storageAccount);
               s.AddScoped(r => blobServiceClient);
           })
           .Build();
    }

    public static void Main(string[] args)
    {
        var host = BuildFunctionHost(args);
        host.Run();
    }
}

记得将Program类的访问修饰符设为public,允许测试项目引用。

步骤2:在测试项目中构建共享测试夹具

使用xUnit的IClassFixture实现宿主实例全局共享,避免重复构建:

// 需先引用NuGet包 Microsoft.Azure.Functions.Worker.Testing
public class FunctionTestFixture : IDisposable
{
    public IHost FunctionHost { get; }
    public HttpClient TestClient { get; }

    public FunctionTestFixture()
    {
        // 传入测试专属启动参数
        FunctionHost = Program.BuildFunctionHost(Array.Empty<string>());
        FunctionHost.StartAsync().Wait();
        // 直接获取内置测试客户端,无需走网络端口
        TestClient = FunctionHost.GetTestHttpClient();
    }

    public void Dispose()
    {
        FunctionHost.DisposeAsync().AsTask().Wait();
    }
}

步骤3:编写测试用例

public class GetAssetFunctionTests : IClassFixture<FunctionTestFixture>
{
    private readonly HttpClient _testClient;

    public GetAssetFunctionTests(FunctionTestFixture fixture)
    {
        _testClient = fixture.TestClient;
    }

    [Fact]
    public async Task GetAsset_缺少查询参数_返回400错误()
    {
        var response = await _testClient.GetAsync("/api/GetAsset");
        Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task GetAsset_参数合法_返回成功结果()
    {
        var response = await _testClient.GetAsync("/api/GetAsset?container=test&blob=demo.txt");
        response.EnsureSuccessStatusCode();
        var result = await response.Content.ReadAsStringAsync();
        Assert.Equal("Success", result);
    }
}

方案二:外部func.exe启动测试(适用于端到端测试)

如果必须使用外部宿主进程的测试方式,可以通过监听标准输出+重试请求解决就绪判断问题:

var startInfo = new ProcessStartInfo("func", "host start --no-build")
{
    WorkingDirectory = @"你的函数项目根目录路径",
    RedirectStandardOutput = true,
    UseShellExecute = false,
    CreateNoWindow = true
};
var funcProcess = Process.Start(startInfo);

// 监听启动日志判断是否就绪
bool hostReady = false;
while (!funcProcess.StandardOutput.EndOfStream)
{
    var outputLine = funcProcess.StandardOutput.ReadLine();
    if (outputLine.Contains("Worker process started and initialized"))
    {
        hostReady = true;
        break;
    }
}

// 额外加重试逻辑兜底,避免日志漏判
if (hostReady)
{
    var httpClient = new HttpClient { BaseAddress = new Uri("http://localhost:7071") };
    int retryCount = 0;
    while (retryCount < 10)
    {
        try
        {
            var response = await httpClient.GetAsync("/api/GetAsset?container=test&blob=test");
            if (response.IsSuccessStatusCode) break;
        }
        catch
        {
            await Task.Delay(1000);
        }
        retryCount++;
        if (retryCount == 10) throw new TimeoutException("函数宿主启动超时");
    }
}

// 测试结束后记得销毁进程,避免端口占用
funcProcess.Kill();

内容的提问来源于stack exchange,提问作者Francis Ducharme

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.18 23:54:01