.NET API集成测试首次执行后数据库被清空问题求助
.NET API集成测试数据库数据丢失问题排查与解决
问题描述
我在为.NET API编写首个集成测试时遇到异常:首次测试执行后数据库被清空。原本预期IClassFixture能保证测试间数据持久,但初始数据库中的用户在login测试执行后消失,运行CreateNote测试时,AcquireToken方法内的Assert.True(result.IsSuccessStatusCode)断言失败,返回False。
相关代码
CustomWebApplicationFactory
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { var dbContextDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<DataContext>)); if(dbContextDescriptor != null) services.Remove(dbContextDescriptor); var dbConnectionDescriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbConnection) ); if(dbConnectionDescriptor != null) services.Remove(dbConnectionDescriptor); // Create open SqliteConnection so EF won't automatically close it. services.AddSingleton<DbConnection>(container => { var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); return connection; }); services.AddDbContext<DataContext>( (container, options) => { var connection = container.GetRequiredService<DbConnection>(); options.UseSqlite(connection); } ); }); builder.ConfigureTestServices(services => { //services.AddSingleton<ITokenService, TokenService>(); services.AddSingleton<ILoggerFactory, NullLoggerFactory>(); }); builder.UseEnvironment("Development"); } }
HttpClientTest
public abstract class HttpClientTest : IClassFixture<CustomWebApplicationFactory<Program>> , IDisposable { protected readonly HttpClient _httpClient; protected readonly JsonSerializerOptions _serializerOptions; protected readonly CustomWebApplicationFactory<Program> _factory; public HttpClientTest(CustomWebApplicationFactory<Program> factory) { _factory = factory; _httpClient = factory.CreateClient( new WebApplicationFactoryClientOptions { AllowAutoRedirect = false } ); _serializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; #pragma warning disable CS4014 SetupTestData(); #pragma warning restore CS4014 } private async Task SetupTestData() { using var scope = _factory.Services.CreateScope(); var scopedServices = scope.ServiceProvider; var db = scopedServices.GetRequiredService<DataContext>(); await Utilities.InitializeDbForTests(db); } protected async Task<string> AcquireToken() { var d = new LoginDto { Email = "jan@example.com", Password = "Pa$$w0rd" }; var content = JsonSerializer.Serialize(d); var result = await DoPost("/api/patients/account/login", content); Assert.True(result.IsSuccessStatusCode); Assert.Equal("application/json", result.Content.Headers.ContentType!.MediaType); var resultJson = await result.Content.ReadAsStringAsync(); var resultObj = JsonSerializer.Deserialize<PatientDto>(resultJson, _serializerOptions); Assert.NotNull(resultObj); Assert.NotNull(resultObj.Token); return resultObj.Token; } protected async Task<HttpResponseMessage> DoGet(string url) { return await _httpClient.GetAsync(url); //.ConfigureAwait(false); } protected async Task<HttpResponseMessage> DoGetAuthenticated(string url, string token) { var client = _factory.CreateClient(); if (token != null) { client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); } var result = await client.GetAsync(url); //ConfigureAwait(false); return result; } protected async Task<HttpResponseMessage> DoPostAuthenticated( string url, string token, string content ) { var client = _factory.CreateClient(); if (token != null) { client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); } return await client .PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json")) .ConfigureAwait(false); } protected async Task<HttpResponseMessage> DoPost(string url, string content) { return await _httpClient .PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json")) .ConfigureAwait(false); } protected async Task<HttpResponseMessage> DoPutAuthenticated(string url, string token, string content) { var client = _factory.CreateClient(); if (token != null) { client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); } return await client .PutAsync(url, new StringContent(content, Encoding.UTF8, "application/json")) .ConfigureAwait(false); } public void Dispose() { GC.SuppressFinalize(this); } }
Tests
public class PatientsControllerIntegrationTests : HttpClientTest { public PatientsControllerIntegrationTests(CustomWebApplicationFactory<Program> factory) : base(factory) { } [Fact] public async Task login() { var d = new LoginDto { Email = "jan@example.com", Password = "Pa$$w0rd" }; var content = JsonSerializer.Serialize(d); var result = await DoPost("/api/patients/account/login", content); Assert.True(result.IsSuccessStatusCode); Assert.Equal("application/json", result.Content.Headers.ContentType!.MediaType); } [Fact] public async Task CreateNote() { var n = new NoteDto{Description = "Test", Value = 1}; var content = JsonSerializer.Serialize(n); var token = await AcquireToken(); var result = await DoPostAuthenticated("/api/notes/1", token, content); Assert.True(result.IsSuccessStatusCode); } }
错误信息
Assert.True() Failure Expected: True Actual: False 调用栈: at APITests.HttpClientTest.AcquireToken() in C:\Users\karol\Desktop\praca inż\APITests\HttpClientTest.cs:line 50 at APITests.PatientsControllerIntegrationTests.CreateNote() in C:\Users\karol\Desktop\praca inż\APITests\PatientsControllerIntegrationTests.cs:line 40
问题根源与解决方案
1. 异步初始化未等待导致数据未就绪
构造函数中调用SetupTestData()时未等待异步操作完成(通过#pragma warning disable CS4014忽略了警告),导致测试可能在数据初始化完成前就开始执行,或者初始化操作被中断。
修复方式:改用xUnit的IAsyncLifetime接口处理异步初始化,xUnit会等待InitializeAsync完成后再执行测试:
public abstract class HttpClientTest : IClassFixture<CustomWebApplicationFactory<Program>>, IAsyncLifetime, IDisposable { // 原有字段和构造函数保持不变 public async Task InitializeAsync() { await SetupTestData(); } // 原有SetupTestData及其他方法保持不变 public Task DisposeAsync() { Dispose(); return Task.CompletedTask; } }
2. 测试并行执行导致数据竞争
xUnit默认会并行执行测试,多个测试同时操作共享的内存数据库,可能出现一个测试清空/修改数据、另一个测试读取到异常数据的情况。
修复方式:在测试项目根目录添加xunit.runner.json文件,禁止并行执行:
{ "parallelizeTestCollections": false, "parallelizeAssembly": false }
并将该文件的「复制到输出目录」设置为「如果较新则复制」。
3. 调试定位具体错误
在AcquireToken方法中添加日志,查看登录请求的具体响应内容,明确失败原因(比如用户不存在、密码错误等):
protected async Task<string> AcquireToken() { var d = new LoginDto { Email = "jan@example.com", Password = "Pa$$w0rd" }; var content = JsonSerializer.Serialize(d); var result = await DoPost("/api/patients/account/login", content); // 添加以下代码查看错误详情 if (!result.IsSuccessStatusCode) { var errorContent = await result.Content.ReadAsStringAsync(); Console.WriteLine($"登录失败,状态码:{result.StatusCode},内容:{errorContent}"); } Assert.True(result.IsSuccessStatusCode); // 原有代码保持不变 }
内容的提问来源于stack exchange,提问作者sharls
相关产品推荐
相关产品推荐

