集成测试中无法替换AppDbContext为内存SQLite数据库报错
问题描述
我准备了极简代码复现问题,在为Minimal API编写集成测试时,无法将生产环境的AppDbContext替换为测试用内存数据库,执行测试时出现以下错误:
Api.Test.TodoEndpoint_IntegrationTest.NumberOfTodos_MustBe_Two Source: TodoEndpoint_IntegrationTest.cs line 44 Duration: 1.1 sec Message: Microsoft.Data.Sqlite.SqliteException : SQLite Error 1: 'no such table: Todos'. Stack Trace: SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db) SqliteCommand.PrepareAndEnumerateStatements()+MoveNext() SqliteCommand.GetStatements()+MoveNext() SqliteDataReader.NextResult() SqliteCommand.ExecuteReader(CommandBehavior behavior) SqliteCommand.ExecuteDbDataReader(CommandBehavior behavior) RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject) Enumerator.InitializeReader(Enumerator enumerator) <>c.<MoveNext>b__21_0(DbContext _, Enumerator enumerator) NonRetryingExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded) Enumerator.MoveNext() Enumerable.TryGetSingle[TSource](IEnumerable`1 source, Boolean& found) lambda_method157(Closure, QueryContext) QueryCompiler.Execute[TResult](Expression query) EntityQueryProvider.Execute[TResult](Expression expression) TodoEndpoint_IntegrationTest.NumberOfTodos_MustBe_Two() line 50 RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
生产环境AppDbContext配置
Program.cs相关代码:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<AppDbContext>(options => { var constr = builder.Configuration.GetConnectionString("DefaultConnection"); options.UseSqlite(constr); }); builder.Services.AddScoped<TodoRepo>(); var app = builder.Build(); // 其他代码已简化
appsettings.json连接字符串:
"ConnectionStrings": { "DefaultConnection": "DataSource=Api.db" }
集成测试AppDbContext配置
CustomWebApplicationFactory.cs相关代码:
public sealed class CustomWebApplicationFactory : WebApplicationFactory<Program> { protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); builder.ConfigureServices(isc => { var descriptor = isc.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<AppDbContext>)); if (descriptor != null) { isc.Remove(descriptor); } isc.AddDbContext<AppDbContext>(options => { var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); options.UseSqlite(connection); }); }); } // 其他代码已简化 }
集成测试代码
TodoEndpoint_IntegrationTest.cs相关代码:
// 依赖项已简化 [Theory] [InlineData(1)] [InlineData(2)] public async Task GetTodoById_Returns_OK(int id) { // act var response = await _client.GetAsync($"/todos/{id}"); // assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var content = await response.Content.ReadAsStringAsync(); Assert.NotNull(content); } [Theory] [InlineData(3)] public async Task GetTodoById_Returns_NotFound(int id) { // act var response = await _client.GetAsync($"/todos/{id}"); // assert Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Fact] public void NumberOfTodos_MustBe_Two() { using (var scope = _factory.Services.CreateScope()) { if (scope.ServiceProvider.GetRequiredService<AppDbContext>() is AppDbContext context) { var count = context.Todos.Count(); Assert.Equal(2, count); } } }
解决方案
错误原因是内存SQLite数据库未创建表结构,也未初始化测试数据。修改CustomWebApplicationFactory,在替换DbContext后添加数据库初始化逻辑:
public sealed class CustomWebApplicationFactory : WebApplicationFactory<Program> { protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); builder.ConfigureServices(isc => { // 移除原有DbContext配置 var descriptor = isc.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<AppDbContext>)); if (descriptor != null) { isc.Remove(descriptor); } // 创建内存SQLite连接并保持打开 var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); // 注册测试用DbContext isc.AddDbContext<AppDbContext>(options => { options.UseSqlite(connection); }); // 初始化数据库表和测试数据 using var serviceScope = isc.BuildServiceProvider().CreateScope(); var dbContext = serviceScope.ServiceProvider.GetRequiredService<AppDbContext>(); // 根据实体模型创建表结构 dbContext.Database.EnsureCreated(); // 添加测试数据 dbContext.Todos.AddRange( new Todo { Id = 1, Title = "测试待办1", Completed = false }, new Todo { Id = 2, Title = "测试待办2", Completed = true } ); dbContext.SaveChanges(); }); } }
关键说明
- 保持内存SQLite连接打开:内存SQLite的数据生命周期与连接绑定,连接关闭后数据会丢失,必须在工厂中维持连接处于打开状态。
- 初始化表结构:通过
dbContext.Database.EnsureCreated()根据实体模型自动创建表,无需手动编写SQL迁移。 - 添加测试数据:创建表后插入预期的测试数据,确保测试时数据库中有符合断言要求的记录。
修改后重新运行测试即可正常通过。
内容的提问来源于stack exchange,提问作者user23207089
相关产品推荐
相关产品推荐

