如何确保.NET集成测试中OnModelCreating始终被触发
问题:批量运行集成测试时OnModelCreating未重复触发导致行为不一致
问题现象
使用.NET 8的WebApplicationFactory编写仓储类集成测试时,出现行为不一致问题:
- 单独运行每个测试用例时,
TodoContext的OnModelCreating方法会正常触发 - 批量运行所有测试用例时,部分测试的
OnModelCreating未触发,导致测试结果不符合预期
相关代码
Program.cs
using Microsoft.EntityFrameworkCore; namespace WebApplicationContextIssue; public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext<TodoContext>(options => options.UseInMemoryDatabase("TodoList")); // 使用内存数据库简化测试 builder.Services.AddScoped<ITodoRepository, TodoRepository>(); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseAuthorization(); app.MapControllers(); app.Run(); } }
仓储类(TodoRepository.cs)
using Microsoft.EntityFrameworkCore; namespace WebApplicationContextIssue; public class TodoRepository : ITodoRepository { private readonly TodoContext _context; public TodoRepository(TodoContext context) { _context = context; } public async Task<IEnumerable<TodoItem>> GetAllAsync() { return await _context.TodoItems.ToListAsync(); } public async Task<TodoItem?> GetByIdAsync(int id) { return await _context.TodoItems.FindAsync(id); } public async Task AddAsync(TodoItem item) { await _context.TodoItems.AddAsync(item); await _context.SaveChangesAsync(); } }
DbContext类(TodoContext.cs)
using Microsoft.EntityFrameworkCore; using WebApplicationContextIssue; public class TodoContext : DbContext { private readonly ILogger _logger; public TodoContext(DbContextOptions<TodoContext> options, ILogger<TodoContext> logger) : base(options) { _logger = logger; _logger.LogInformation("TodoContext!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); } public DbSet<TodoItem> TodoItems { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { _logger.LogInformation("OnModelCreating!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); base.OnModelCreating(modelBuilder); modelBuilder.Entity<TodoItem>().HasKey(t => t.Id); } }
测试类(TodoRepositoryTests.cs)
using ConnectingApps.Xunit.TestLogger; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Xunit.Abstractions; namespace WebApplicationContextIssue.IntegrationTest; public class TodoRepositoryTests { private readonly WebApplicationFactory<Program> _factory; public TodoRepositoryTests(ITestOutputHelper output) { _factory = new TestLoggerWebApplicationFactory<Program>(output); } [Theory] [InlineData("title1")] [InlineData("title2")] public async Task AddAsync_AddsTodoItemSuccessfully(string title) { var todoItem = new TodoItem { Title = title, IsCompleted = false }; using (var scope = _factory.Services.CreateScope()) { var services = scope.ServiceProvider; var repository = services.GetRequiredService<ITodoRepository>(); await repository.AddAsync(todoItem); var addedItem = await repository.GetByIdAsync(todoItem.Id); Assert.NotNull(addedItem); Assert.Equal(todoItem.Title, addedItem.Title); Assert.Equal(todoItem.IsCompleted, addedItem.IsCompleted); } } }
日志对比
单独运行测试日志(符合预期)
2024-09-01 11:49:32 [Information] TodoContext!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2024-09-01 11:49:33 [Information] OnModelCreating!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2024-09-01 11:49:33 [Information] Saved 1 entities to in-memory store.
2024-09-01 11:49:37 [Information] TodoContext!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2024-09-01 11:49:38 [Information] OnModelCreating!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2024-09-01 11:49:38 [Information] Saved 1 entities to in-memory store.
批量运行测试日志(不符合预期)
2024-09-01 12:13:57 [Information] TodoContext!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2024-09-01 12:13:58 [Information] OnModelCreating!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2024-09-01 12:13:58 [Information] Saved 1 entities to in-memory store.
2024-09-01 12:13:58 [Information] TodoContext!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2024-09-01 12:13:58 [Information] Saved 1 entities to in-memory store.
解决方案
问题根源
EF Core会缓存DbContext的模型元数据,当多个测试共享同一个内存数据库实例(相同名称)时,模型只会初始化一次,OnModelCreating仅触发一次。批量测试时,后续测试复用了已缓存的模型,导致方法不再执行。
解决步骤
修改测试类中的WebApplicationFactory配置,为每个测试创建独立的内存数据库并禁用模型缓存,确保每次创建TodoContext都触发OnModelCreating。
修改后的测试类代码:
using ConnectingApps.Xunit.TestLogger; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Xunit.Abstractions; using Microsoft.EntityFrameworkCore; namespace WebApplicationContextIssue.IntegrationTest; public class TodoRepositoryTests { private readonly WebApplicationFactory<Program> _factory; public TodoRepositoryTests(ITestOutputHelper output) { _factory = new TestLoggerWebApplicationFactory<Program>(output) { ConfigureServices = services => { // 移除原有DbContext配置 var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<TodoContext>)); if (descriptor != null) { services.Remove(descriptor); } // 为每个测试生成唯一的内存数据库名称,并禁用模型缓存 services.AddDbContext<TodoContext>(options => { options.UseInMemoryDatabase(Guid.NewGuid().ToString()); // 强制每次创建DbContext时重新构建模型,触发OnModelCreating options.UseModel(null); }); } }; } [Theory] [InlineData("title1")] [InlineData("title2")] public async Task AddAsync_AddsTodoItemSuccessfully(string title) { var todoItem = new TodoItem { Title = title, IsCompleted = false }; using (var scope = _factory.Services.CreateScope()) { var services = scope.ServiceProvider; var repository = services.GetRequiredService<ITodoRepository>(); await repository.AddAsync(todoItem); var addedItem = await repository.GetByIdAsync(todoItem.Id); Assert.NotNull(addedItem); Assert.Equal(todoItem.Title, addedItem.Title); Assert.Equal(todoItem.IsCompleted, addedItem.IsCompleted); } } }
效果验证
修改后无论单独还是批量运行测试,每个测试用例的日志都会显示OnModelCreating被触发,测试行为保持一致。
内容的提问来源于stack exchange,提问作者Daan
相关产品推荐
相关产品推荐

