ASP.NET Core 7 Web API集成测试:WithWebHostBuilder致数据库异常
问题根源
每次调用WithWebHostBuilder都会生成全新的WebApplicationFactory实例,而你自定义工厂中配置的内存SQLite连接是与原实例绑定的。新实例会重新初始化数据库上下文,导致测试代码插入数据的连接和API处理请求的连接不是同一个,自然查不到数据。切换为文件SQLite时生成两个文件,就是因为新旧实例各自创建了独立的数据库文件/连接。
解决方案
1. 复用同一个内存SQLite连接
内存SQLite默认Data Source=:memory:会为每个新连接创建独立内存库,要共享必须使用带共享缓存的连接字符串,并保持连接打开(内存库会在所有连接关闭后销毁)。
修改自定义WebApplicationFactory
public class CustomWebApplicationFactory<TProgram> : WebApplicationFactory<TProgram> where TProgram : class { private const string SharedSqliteConnStr = "Data Source=file::memory:?cache=shared"; private SqliteConnection? _sharedConnection; protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { // 移除原有DbContext注册 var dbContextOption = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<YourDbContext>)); if (dbContextOption != null) services.Remove(dbContextOption); // 创建并打开共享连接 _sharedConnection = new SqliteConnection(SharedSqliteConnStr); _sharedConnection.Open(); // 注册DbContext使用共享连接 services.AddDbContext<YourDbContext>(options => options.UseSqlite(_sharedConnection)); }); } protected override void Dispose(bool disposing) { base.Dispose(disposing); _sharedConnection?.Close(); } }
2. 测试中统一使用带Mock配置的工厂实例
避免多次调用WithWebHostBuilder生成新实例,而是在测试初始化时创建一次带Mock配置的工厂,后续所有测试复用它:
public class YourApiTests : IClassFixture<CustomWebApplicationFactory<Program>> { private readonly HttpClient _client; private readonly CustomWebApplicationFactory<Program> _testFactory; public YourApiTests(CustomWebApplicationFactory<Program> factory) { // 仅创建一次带Mock配置的工厂实例 _testFactory = factory.WithWebHostBuilder(builder => { builder.ConfigureServices(services => { // Mock外部HTTP调用的HttpClient services.AddHttpClient("ExternalService") .ConfigurePrimaryHttpMessageHandler(() => { var mockHandler = new Mock<HttpMessageHandler>(); mockHandler.Protected() .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>()) .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{\"result\": \"mocked-data\"}") }); return mockHandler.Object; }); }); }); _client = _testFactory.CreateClient(); } [Fact] public async Task InsertAndQuery_ShouldReturnData() { // 从_testFactory获取上下文插入数据 using var scope = _testFactory.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<YourDbContext>(); dbContext.Entities.Add(new YourEntity { Name = "Test" }); await dbContext.SaveChangesAsync(); // 用同一个客户端调用API查询 var response = await _client.GetAsync("/api/entities"); response.EnsureSuccessStatusCode(); var entities = await response.Content.ReadFromJsonAsync<List<YourEntity>>(); Assert.Single(entities); } }
3. 可选:将Mock逻辑内置到自定义工厂
如果所有测试的Mock规则一致,可以直接在CustomWebApplicationFactory的ConfigureWebHost中配置HttpClient,测试时直接用原工厂创建客户端,无需调用WithWebHostBuilder。
内容的提问来源于stack exchange,提问作者Jakub Kopyś
相关产品推荐
相关产品推荐

