ASP.NET Core Web API单元测试中DbContext条件注册失效问题
问题描述
使用C#开发ASP.NET Core Web API时,在NUnit测试项目中模拟Web应用遇到错误:服务提供者中同时注册了Microsoft.EntityFrameworkCore.SqlServer与Microsoft.EntityFrameworkCore.InMemory数据库提供者,仅允许注册单个。已在Program类中通过IsUnitTest配置项编写条件注册逻辑,但测试时该配置项取值不符合预期(返回null),尝试移除服务描述符也无效,现需实现仅在非测试环境注册SqlServer的DbContext。
Program类代码
public partial class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var logger = LoggerFactory.Create(logging => logging.AddConsole()).CreateLogger<Program>(); var isUnitTest = builder.Configuration.GetValue<bool>("IsUnitTest"); logger.LogInformation("IsUnitTest: {IsUnitTest}", isUnitTest); if (isUnitTest) { builder.Services.AddDbContext<API.Context.APIContext>(options => options.UseInMemoryDatabase("InMemoryDbForTesting")); } else { var conString = builder.Configuration.GetConnectionString("DatabaseConnection") ?? throw new InvalidOperationException("Connection string 'DatabaseConnection' not found."); builder.Services.AddDbContext<API.Context.APIContext>(options => options.UseSqlServer(conString)); } // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); }; app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.MapServiceEndpoints(); app.MapScanEndpoints(); app.MapPackageEndpoints(); app.MapImageEndpoints(); app.Run(); } }
单元测试代码
private WebApplicationFactory<Program>? _factory; private HttpClient? _client; [SetUp] public void Setup() { _factory = new WebApplicationFactory<Program>() .WithWebHostBuilder(builder => { builder.ConfigureAppConfiguration((context, config) => { var settings = new Dictionary<string, string> { { "IsUnitTest", "true" } }; config.AddInMemoryCollection(settings); }); builder.ConfigureServices(services => { // Remove all DbContextOptions<APIContext> descriptors var descriptors = services.Where(d => d.ServiceType == typeof(DbContextOptions<APIContext>)).ToList(); foreach (var descriptor in descriptors) { services.Remove(descriptor); } services.AddDbContext<APIContext>(options => { options.UseInMemoryDatabase("InMemoryDbForTesting"); }); // Add logging services.AddLogging(configure => configure.AddConsole()); }); }); _client = _factory.CreateClient(); }
解决方案
方案1:修复配置项读取问题,确保测试配置优先
测试中添加的IsUnitTest配置被原有配置源覆盖,导致Program中读取不到正确值。修改测试代码的配置逻辑,清除原有配置源后再添加测试配置:
builder.ConfigureAppConfiguration((context, config) => { config.Sources.Clear(); // 清除默认配置源,确保测试配置优先 var settings = new Dictionary<string, string> { { "IsUnitTest", "true" } }; config.AddInMemoryCollection(settings); });
这样Program中的isUnitTest会正确读取到true,自动注册InMemory数据库,不会触发SqlServer的注册逻辑,从根源避免冲突。
方案2:彻底移除原有DbContext注册,重新添加测试用实例
如果不想修改配置逻辑,可在测试中彻底移除所有与APIContext相关的服务注册,再重新添加InMemory版本:
builder.ConfigureServices(services => { // 移除APIContext本身及对应的DbContextOptions配置 var descriptors = services.Where(d => d.ServiceType == typeof(APIContext) || d.ServiceType == typeof(DbContextOptions<APIContext>)).ToList(); foreach (var descriptor in descriptors) { services.Remove(descriptor); } // 重新注册InMemory数据库的DbContext services.AddDbContext<APIContext>(options => { options.UseInMemoryDatabase("InMemoryDbForTesting"); }); services.AddLogging(configure => configure.AddConsole()); });
方案3:使用环境标识替代配置项(更简洁)
在Program中通过环境判断替代配置项,测试时指定测试环境:
修改Program类的DbContext注册逻辑
// 替换原来的IsUnitTest判断 if (builder.Environment.IsEnvironment("Testing")) { builder.Services.AddDbContext<API.Context.APIContext>(options => options.UseInMemoryDatabase("InMemoryDbForTesting")); } else { var conString = builder.Configuration.GetConnectionString("DatabaseConnection") ?? throw new InvalidOperationException("Connection string 'DatabaseConnection' not found."); builder.Services.AddDbContext<API.Context.APIContext>(options => options.UseSqlServer(conString)); }
修改测试代码,指定测试环境
_factory = new WebApplicationFactory<Program>() .WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); // 设置测试环境 builder.ConfigureServices(services => { services.AddLogging(configure => configure.AddConsole()); }); });
这种方式无需配置项,直接通过环境标识区分测试与生产环境,逻辑更清晰。
内容的提问来源于stack exchange,提问作者Hiromi
相关产品推荐
相关产品推荐

