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

配置UseInMemoryDatabase后,集成测试仍连接主MSSQL数据库?

测试数据写入主MSSQL数据库的原因及解决方法

问题场景

我正在为API搭建集成测试环境,通过以下代码重写服务以使用内存数据库(InMemoryDatabase):

public class IntegrationTest
{
    protected readonly HttpClient TestClient;

    protected IntegrationTest()
    {
        var applicationFactory = new WebApplicationFactory<Program>().WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                services.RemoveAll(typeof(DataContext));
                services.AddDbContext<DataContext>(options =>
                {
                    options.UseInMemoryDatabase("Test Db");
                });
            });
        });

        TestClient = applicationFactory.CreateClient();
    }

    protected async Task AuthenticateAsync()
    {
        TestClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", await GetJwtAsync());
    }

    protected async Task<Category> CreateCategoryAsync(Category category)
    {
        var response = await TestClient.PostAsJsonAsync("api/Categories", category);
        return await response.Content.ReadAsAsync<Category>();
    }
    private async Task<string> GetJwtAsync()
    {
        var response = await TestClient.PostAsJsonAsync("api/Categories", new CategoryDTO { CategoryName = "Test category" });
        var postResponse = await response.Content.ReadAsAsync<CategoryDTO>();
        return postResponse.CategoryName;
    }
}

示例测试代码:

[Fact]
public async Task Get_ReturnCategory()
{
    //Arrange
    await AuthenticateAsync();
    var createdCategory = await CreateCategoryAsync(new Category { CategoryName = "Intest category"});

    //Act
    var response = await TestClient.GetAsync($"api/Categories/{createdCategory.Id.ToString()}");

    //Assert
    response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
    var returnedCategory = await response.Content.ReadAsAsync<Category>();
    returnedCategory.Should().Be(createdCategory.Id);
    returnedCategory.CategoryName.Should().Be("Intest category");
}

但测试生成的新数据仍会出现在主MSSQL数据库中,请问原因是什么?该如何解决?

原因分析

  1. DbContext注册未被彻底替换:仅调用services.RemoveAll(typeof(DataContext))无法移除所有与DataContext相关的注册项,比如原项目中通过AddDbContextPool注册的池化DbContext,或者关联的DbContextOptions<DataContext>服务仍保留着原MSSQL的配置,导致应用还是使用主数据库连接。
  2. 配置文件未隔离:测试环境可能加载了原项目的appsettings.json配置,其中的数据库连接字符串优先级高于内存数据库的配置,使得DbContext仍指向主MSSQL。
  3. 认证逻辑错误:GetJwtAsync方法通过创建Category获取名称作为JWT Token,不符合正常认证流程,可能导致认证失败,进而接口使用了未被替换的DbContext实例。

解决方法

1. 彻底替换DbContext及关联配置

修改ConfigureServices中的代码,确保移除所有与DataContext相关的注册,包括DbContextOptions<DataContext>:

builder.ConfigureServices(services =>
{
    // 移除DataContext的注册
    var dataContextDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DataContext));
    if (dataContextDescriptor != null)
    {
        services.Remove(dataContextDescriptor);
    }

    // 移除DbContextOptions的注册,避免残留主库配置
    var optionsDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<DataContext>));
    if (optionsDescriptor != null)
    {
        services.Remove(optionsDescriptor);
    }

    // 添加内存数据库配置,用Guid生成唯一名称避免测试间数据污染
    services.AddDbContext<DataContext>(options =>
    {
        options.UseInMemoryDatabase($"TestDb_{Guid.NewGuid()}");
    });
});

2. 隔离测试环境的配置文件

在构建WebApplicationFactory时,清除原有配置源,使用测试专属配置,避免读取原项目的数据库连接字符串:

var applicationFactory = new WebApplicationFactory<Program>()
    .WithWebHostBuilder(builder =>
    {
        builder.ConfigureAppConfiguration((context, config) =>
        {
            // 清除默认配置源,防止加载原项目的appsettings.json
            config.Sources.Clear();
            // 按需添加测试用配置项
            config.AddInMemoryCollection(new Dictionary<string, string>
            {
                // 例如:"Jwt:Secret": "test-secret-key"
            });
        });

        // 替换DbContext的代码(同上)
        builder.ConfigureServices(services =>
        {
            // ...
        });
    });

3. 修正认证逻辑

GetJwtAsync方法需调用API的合法认证接口获取JWT Token,示例如下:

private async Task<string> GetJwtAsync()
{
    var loginRequest = new LoginDTO { Username = "testuser", Password = "testpass" };
    var response = await TestClient.PostAsJsonAsync("api/Auth/Login", loginRequest);
    response.EnsureSuccessStatusCode();
    var loginResponse = await response.Content.ReadAsAsync<AuthResponseDTO>();
    return loginResponse.Token;
}

4. 确保测试隔离性

每个测试方法使用独立的IntegrationTest实例,或在测试结束后清理内存数据库,避免数据残留影响其他测试。


内容的提问来源于stack exchange,提问作者Palamar66

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 05:45:39