ASP.NET Core集成测试中SQLite内存库上下文被释放问题排查
问题根源分析
你遇到的ObjectDisposedException核心原因非常明确:测试构造函数里的using var scope导致Scope在构造函数执行完毕后就被自动释放,而Scoped生命周期的AppDbContext会随着Scope的销毁被Dispose。当你在测试方法里调用_context.Customers.Add()时,这个上下文已经是被释放的状态,自然会报错。
另外你的CustomWebApplicationFactory还有一点小问题:没有确保SQLite内存数据库被正确初始化,这可能导致后续测试中数据库结构不存在的问题。
解决方案
方案1:调整测试类的Scope生命周期
把Scope保存为测试类的字段,避免在构造函数中提前释放,同时手动实现IDisposable接口来清理资源:
public class OrderControllerTests : IClassFixture<CustomWebApplicationFactory<Startup>>, IDisposable { private readonly HttpClient _httpClient; private readonly IServiceScope _scope; // 保存Scope,避免提前释放 private readonly AppDbContext _context; public OrderControllerTests(CustomWebApplicationFactory<Startup> factory) { _httpClient = factory.CreateDefaultClient(); var scopeFactory = factory.Services.GetRequiredService<IServiceScopeFactory>(); _scope = scopeFactory.CreateScope(); // 去掉using,延长Scope生命周期 _context = _scope.ServiceProvider.GetRequiredService<AppDbContext>(); } [Fact] public async Task Create_Post_OrderIsCreated() { // 准备测试数据 var customer = new Customer { /* 初始化字段 */ }; _context.Customers.Add(customer); await _context.SaveChangesAsync(); // 构造POST请求并发送 var orderRequest = new OrderCreateRequest { CustomerId = customer.Id /* 其他字段 */ }; var jsonContent = JsonContent.Create(orderRequest); var response = await _httpClient.PostAsync("/Order/Create", jsonContent); // 验证响应和数据库状态 response.EnsureSuccessStatusCode(); var createdOrder = await _context.Orders.FirstOrDefaultAsync(o => o.CustomerId == customer.Id); Assert.NotNull(createdOrder); } // 手动释放Scope和上下文 public void Dispose() { _scope.Dispose(); } }
方案2:在测试方法内创建Scope(更推荐)
遵循依赖注入的最佳实践,在每个测试方法内部创建独立的Scope和DbContext,这样每个测试的资源都是隔离的,避免测试间的副作用:
public class OrderControllerTests : IClassFixture<CustomWebApplicationFactory<Startup>> { private readonly HttpClient _httpClient; private readonly CustomWebApplicationFactory<Startup> _factory; public OrderControllerTests(CustomWebApplicationFactory<Startup> factory) { _httpClient = factory.CreateDefaultClient(); _factory = factory; } [Fact] public async Task Create_Post_OrderIsCreated() { // 在测试方法内创建Scope和上下文 using var scope = _factory.Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<AppDbContext>(); // 准备测试数据 var customer = new Customer { /* 初始化字段 */ }; context.Customers.Add(customer); await context.SaveChangesAsync(); // 构造POST请求并发送 var orderRequest = new OrderCreateRequest { CustomerId = customer.Id /* 其他字段 */ }; var jsonContent = JsonContent.Create(orderRequest); var response = await _httpClient.PostAsync("/Order/Create", jsonContent); // 验证响应和数据库状态 response.EnsureSuccessStatusCode(); var createdOrder = await context.Orders.FirstOrDefaultAsync(o => o.CustomerId == customer.Id); Assert.NotNull(createdOrder); } }
完善CustomWebApplicationFactory配置
确保SQLite内存数据库被正确初始化,并且Connection在整个测试周期内保持打开:
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class { private SqliteConnection _connection; protected override void ConfigureWebHost(IWebHostBuilder builder) { _connection = new SqliteConnection("DataSource=:memory:"); _connection.Open(); builder.UseEnvironment("Development"); builder.ConfigureTestServices(services => { // 移除原有的DbContext配置 var descriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbContextOptions<AppDbContext>)); if (descriptor != null) services.Remove(descriptor); // 注册SQLite内存数据库 services.AddDbContext<AppDbContext>(options => options.UseSqlite(_connection)); // 初始化数据库结构 var sp = services.BuildServiceProvider(); using var scope = sp.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<AppDbContext>(); context.Database.EnsureCreated(); }); } protected override void Dispose(bool disposing) { base.Dispose(disposing); _connection?.Close(); _connection?.Dispose(); } }
内容的提问来源于stack exchange,提问作者Muflix
相关产品推荐
相关产品推荐

