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

集成测试共享DbContext时用户更新变更未同步的问题排查

问题

我在编写集成测试时使用TestContainer包(SQL Server),并通过BaseWebApplicationTest抽象类共享HttpClient和AppDbContext等属性。

我的WebApplicationFactory实现如下:

public class IntegrationTestWebApplicationFactory<TProgram>
    : WebApplicationFactory<TProgram>, IAsyncLifetime
    where TProgram : class
{
    private readonly MsSqlContainer _msSqlContainer = new MsSqlBuilder()
        .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
        .Build();

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(services =>
        {
            services.RemoveAll<DbContextOptions<AppDbContext>>();
            services.AddSqlServer<AppDbContext>(_msSqlContainer.GetConnectionString());
        });
    }

    public Task InitializeAsync() => _msSqlContainer.StartAsync();

    public new Task DisposeAsync() => _msSqlContainer.StopAsync();
}

共享属性的抽象类:

public abstract class BaseWebApplicationTest
{
    private readonly IServiceScope _scope;
    protected readonly HttpClient _httpClient;
    protected readonly AppDbContext _context;

    protected BaseWebApplicationTest(IntegrationTestWebApplicationFactory<Program> factory)
    {
        _scope = factory.Services.CreateScope();
        _context = _scope.ServiceProvider.GetRequiredService<AppDbContext>();
        _httpClient = factory.CreateClient();
    }
}

创建用户的测试用例可正常运行,但执行用户更新测试时出现问题:调用更新接口后,测试中的AppDbContext无法获取到变更数据,查询到的仍是旧值。更新测试用例及报错信息如下:

[Fact]
public async Task Update_Should_Return200StatusCode_WhenRequestDataIsValid()
{
    // Arrange
    var role = new Role()
    {
        Name = "admin1x",
        Description = "This is admin user"
    };

    var user = new User()
    {
        FirstName = "First Name",
        LastName = "Last Name",
        Email = "email@example2.com",
        Password = "Pass123@",
        Username = "user_name3",
        Role = role
    };

    await _context.Roles.AddAsync(role);
    await _context.Users.AddAsync(user);
    await _context.SaveChangesAsync();

    CreateUserRequest request = new(
        "Name changed",
        "Last Name changed",
        "user_changed",
        "example_changed@email.com",
        "PassChanged123$",
        RoleId: role.Id);

    // Act
    HttpResponseMessage response = await _httpClient.PutAsJsonAsync(
        $"/api/user/{user.Id}",
        request);
    
    // Assert
    response.EnsureSuccessStatusCode();
    response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);

    Guid id = await response.Content.ReadFromJsonAsync<Guid>();
    id.Should().NotBeEmpty();
    
    var userChanged = await _context.Users.FindAsync(id);

    userChanged?.FirstName.Should().Be(request.FirstName);
    userChanged?.LastName.Should().Be(request.LastName);
    userChanged?.Username.Should().Be(request.UserName);
}

报错信息:

Expected userChanged?.FirstName to be "Name changed" with a length of 12, but "First Name" has a length of 10, differs near "Fir" (index 0).

用户更新服务本身正常,调用_context.Entry(userChanged).Reload();后测试即可通过。请问这是什么原因,该如何解决?

原因与解决方案

原因

EF Core的DbContext自带一级缓存(上下文缓存),测试代码中先通过_context.Users.AddAsync(user)并保存后,这个user对象已经被缓存在当前_context实例中。

当你通过HttpClient调用更新接口时,接口内部会使用独立的新DbContext实例(Web请求会创建新的请求作用域,从容器中获取全新的DbContext)来更新数据库数据,但测试代码中的_context无法感知数据库的变化。此时调用_context.Users.FindAsync(id),EF Core会直接返回缓存里的旧对象,不会去数据库重新查询。

解决方案

有三种常用的解决方式:

方式1:强制重新加载实体

直接调用Reload()方法,强制从数据库刷新指定实体的最新数据:

var userChanged = await _context.Users.FindAsync(id);
_context.Entry(userChanged).Reload(); // 从数据库重新加载实体

userChanged?.FirstName.Should().Be(request.FirstName);

方式2:创建新的DbContext实例

利用现有作用域创建新的DbContext,绕过旧上下文的缓存:

using var newScope = _scope.ServiceProvider.CreateScope();
var newContext = newScope.ServiceProvider.GetRequiredService<AppDbContext>();

var userChanged = await newContext.Users.FindAsync(id);
userChanged?.FirstName.Should().Be(request.FirstName);

方式3:重构抽象类,按需获取新上下文(推荐)

修改BaseWebApplicationTest,避免长期持有同一个DbContext实例,而是提供方法按需创建新的上下文:

public abstract class BaseWebApplicationTest
{
    protected readonly IntegrationTestWebApplicationFactory<Program> _factory;
    protected readonly HttpClient _httpClient;

    protected BaseWebApplicationTest(IntegrationTestWebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _httpClient = factory.CreateClient();
    }

    // 提供获取全新上下文的方法
    protected AppDbContext GetNewDbContext()
    {
        var scope = _factory.Services.CreateScope();
        return scope.ServiceProvider.GetRequiredService<AppDbContext>();
    }
}

测试中使用新上下文查询:

var userChanged = await GetNewDbContext().Users.FindAsync(id);
userChanged?.FirstName.Should().Be(request.FirstName);

这种方式每次查询都使用无缓存的新DbContext,能稳定获取数据库最新数据,适合频繁验证更新结果的测试场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 04:19:51