使用RunOaktonCommands结合WebApplicationFactory测试时触发ObjectDisposedException
ASP.NET 测试中 Oakton 导致的对象已释放异常问题
问题场景
ASP.NET应用程序入口代码:
public sealed class Program { public static async Task Main(params string[] args) { try { var builder = WebApplication.CreateBuilder(args); await using var app = builder .Build(); app.UseRequestLocalization( new RequestLocalizationOptions { DefaultRequestCulture = new RequestCulture("nl-NL"), }); await app.RunOaktonCommands(args); } catch (Exception e) { Console.WriteLine(e); throw; } } }
对应的单元测试代码:
public class Tests : WebApplicationFactory<WebApplication1.Program> { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseEnvironment("UnitTest"); builder.ConfigureLogging(l => l.AddConsole()); } [Test] public async Task Test1() { Server.PreserveExecutionContext = true; using var client = CreateClient(); Assert.Pass(); } }
执行测试时抛出System.ObjectDisposedException: Cannot access a disposed object. Object name: 'IServiceProvider'异常,替换app.RunOaktonCommands(args)为app.RunAsync()后测试正常通过。
原因分析
Oakton的RunOaktonCommands方法是为命令行工具任务设计的,任务执行完成后会直接触发WebApplication实例的释放(代码中await using声明会自动管理实例生命周期)。而WebApplicationFactory在测试流程中需要保持应用服务提供者(IServiceProvider)等资源的存活状态,两者生命周期管理逻辑冲突,导致测试时访问已被释放的对象。
解决方法
方案一:根据环境分支处理入口逻辑
在Main方法中判断当前环境,测试环境下跳过Oakton命令执行,改用RunAsync维持应用生命周期:
public static async Task Main(params string[] args) { try { var builder = WebApplication.CreateBuilder(args); await using var app = builder.Build(); app.UseRequestLocalization( new RequestLocalizationOptions { DefaultRequestCulture = new RequestCulture("nl-NL"), }); // 判断是否为测试环境 if (builder.Environment.IsEnvironment("UnitTest")) { await app.RunAsync(); } else { await app.RunOaktonCommands(args); } } catch (Exception e) { Console.WriteLine(e); throw; } }
方案二:调整WebApplicationFactory的配置(不推荐)
如果必须保留Oakton调用,可尝试在测试中阻止资源提前释放,但这种方式会破坏Oakton的原有设计,容易引入其他问题,因此更推荐方案一。
内容的提问来源于stack exchange,提问作者Gemberkoekje
相关产品推荐
相关产品推荐

