配置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数据库中,请问原因是什么?该如何解决?
原因分析
- DbContext注册未被彻底替换:仅调用
services.RemoveAll(typeof(DataContext))无法移除所有与DataContext相关的注册项,比如原项目中通过AddDbContextPool注册的池化DbContext,或者关联的DbContextOptions<DataContext>服务仍保留着原MSSQL的配置,导致应用还是使用主数据库连接。 - 配置文件未隔离:测试环境可能加载了原项目的
appsettings.json配置,其中的数据库连接字符串优先级高于内存数据库的配置,使得DbContext仍指向主MSSQL。 - 认证逻辑错误:
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
相关产品推荐
相关产品推荐

