You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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ś

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:25:05