基于.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
相关产品推荐
相关产品推荐

