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

如何确保.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 23:59:58