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

集成测试中无法替换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();
        });
    }
}

关键说明

  1. 保持内存SQLite连接打开:内存SQLite的数据生命周期与连接绑定,连接关闭后数据会丢失,必须在工厂中维持连接处于打开状态。
  2. 初始化表结构:通过dbContext.Database.EnsureCreated()根据实体模型自动创建表,无需手动编写SQL迁移。
  3. 添加测试数据:创建表后插入预期的测试数据,确保测试时数据库中有符合断言要求的记录。

修改后重新运行测试即可正常通过。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:39:56