使用WebApplicationFactory的集成测试抛出IServiceProvider已释放异常
问题描述
健康检查测试代码
public class HealthCheckEndpointTests : IClassFixture<ItemsApplicationFactory> { private readonly ItemsApplicationFactory _factory; public HealthCheckEndpointTests(ItemsApplicationFactory factory) { _factory = factory; } public async Task HealthCheck_Test() { // Arrange HttpClient httpClient = _factory.CreateClient(); // Act string response = await httpClient.GetStringAsync("/health/live"); // Assert Assert.Equal("Healthy", response); } }
ItemsApplicationFactory实现代码
public class ItemsApplicationFactory : WebApplicationFactory<Program> { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureKestrel(options => options.AllowSynchronousIO = true); builder.ConfigureServices(services => { // 移除已存在的DbContext配置 var dbContextDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<ItemsDbContext>)); if (dbContextDescriptor != null) services.Remove(dbContextDescriptor); var serviceCollection = new ServiceCollection() .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider(); services.AddDbContext<ItemsDbContext>(mariaDb => { mariaDb.UseInMemoryDatabase("template"); mariaDb.UseInternalServiceProvider(serviceCollection); }); }); } }
测试抛出的异常
System.ObjectDisposedException : Cannot access a disposed object. Object name: 'IServiceProvider'
排查测试代码
修改测试仅初始化HttpClient而不发起请求,测试正常通过:
public class HealthCheckEndpointTests : IClassFixture<ItemsApplicationFactory> { private readonly ItemsApplicationFactory _factory; public HealthCheckEndpointTests(ItemsApplicationFactory factory) { _factory = factory; } public async Task HealthCheck_Test() { // Arrange HttpClient httpClient = _factory.CreateClient(); // Act await Task.Run(() => Task.CompletedTask); // Assert Assert.True(true); } }
疑问
为什么_factory.CreateClient();不会抛出异常,但调用httpClient.GetStringAsync("/health/live")会触发异常?该如何解决?
原因分析与解决方案
原因
你手动创建的serviceCollection.BuildServiceProvider()生成的服务提供器,在ConfigureServices方法执行完成后就会被自动释放。当健康检查接口被调用时,后端处理逻辑需要使用ItemsDbContext,而该DbContext绑定了已释放的服务提供器,因此触发ObjectDisposedException。
_factory.CreateClient()仅创建HttpClient实例,此时并未发起实际请求,也不会触及服务提供器,所以不会抛出异常;只有调用GetStringAsync发起请求后,后端处理流程中用到DbContext,才会触发对已释放服务提供器的访问。
解决方案
不要手动创建独立的服务提供器,直接复用WebApplicationFactory的现有服务容器即可。修改ItemsApplicationFactory的ConfigureServices方法:
public class ItemsApplicationFactory : WebApplicationFactory<Program> { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureKestrel(options => options.AllowSynchronousIO = true); builder.ConfigureServices(services => { // 移除已存在的DbContext配置 var dbContextDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<ItemsDbContext>)); if (dbContextDescriptor != null) services.Remove(dbContextDescriptor); // 直接在现有服务集合中添加内存数据库支持 services.AddEntityFrameworkInMemoryDatabase(); services.AddDbContext<ItemsDbContext>(mariaDb => { mariaDb.UseInMemoryDatabase("template"); // 移除UseInternalServiceProvider调用,使用应用默认服务提供器 }); }); } }
修改后,DbContext会使用WebApplicationFactory管理的服务提供器,其生命周期与测试应用保持一致,不会提前被释放,请求处理时即可正常访问DbContext,异常问题也会解决。
内容的提问来源于stack exchange,提问作者TheSlowestDev
相关产品推荐
相关产品推荐

