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

C#集成测试(SQL Server)未返回预期数据问题排查

问题:软删除集成测试中仓库直接查询与API查询结果不一致

我用XUnit、Moq、FluentAssertions做集成测试,验证RoleModel的软删除操作(仅标记Deleted为true,不删除数据库记录),但测试结果不符合预期。

测试代码如下:

[Fact]
public async Task DeleteAsync_DeletesRoleModelByGivenId()
{
    var fakeRoleModel = new Faker<RoleModel>()
        .RuleFor(r => r.Id, Guid.NewGuid())
        .RuleFor(r => r.Name, "Role Deleted")
        .RuleFor(r => r.Description, f => f.Lorem.Paragraph())
        .RuleFor(r => r.CreatedDate, DateTime.Now)
        .RuleFor(r => r.UpdatedDate, DateTime.Now)
        .RuleFor(r => r.Deleted, false)
        .Generate();

    var roleToDelete = await _rolesRepository.CreateAsync(fakeRoleModel);

    var client = _integrationTestFactory.CreateClient();
    var response = await client.DeleteAsync($"/api/roles/{roleToDelete.Id}");

    response.EnsureSuccessStatusCode();

    var deletedRoleById = await _rolesRepository.GetByIdAsync(roleToDelete.Id);
    deletedRoleById.Deleted.ShouldBeTrue();
}

当前测试在deletedRoleById.Deleted.ShouldBeTrue()处失败:API返回OK,数据库中Deleted已被标记为true,但_rolesRepository.GetByIdAsync获取的对象Deleted仍为false。如果改用API接口查询该角色,测试就能通过:

var secondClient = _integrationTestFactory.CreateClient();
var roleModel = await client.GetFromJsonAsync<RoleModel>($"/api/roles/{roleToDelete.Id}");

roleModel.Name.ShouldBe("Role Deleted");
roleModel.Deleted.ShouldBe(true);

相关代码定义:
RoleModel:

public class RoleModel : BaseRecordModel
{
    public string Name { get; set; }
    public string Description { get; set; }
    public ICollection<RoleFeatureModel> RoleFeatures { get; set; }
    public ICollection<RoleUserModel> RoleUsers { get; set; }
}

仓库DeleteAsync方法:

public virtual async Task DeleteAsync(Guid id)
{
    var entity = await GetEntityByIdAsync(id);
    entity.Deleted = true;
    _db.Set<TEntity>().Update(entity);

    await _db.SaveChangesAsync();
} 

仓库GetByIdAsync方法:

public override async Task<RoleModel> GetByIdAsync(Guid Id)
{
    var roleEntity = await _db.Roles
                         .Include(r => r.RoleFeatures)
                             .ThenInclude(rf => rf.Feature)
                         .FirstOrDefaultAsync(r => r.Id == Id);

    return _mapper.Map<RoleModel>(roleEntity);
}

原因分析

核心问题是EF Core的ChangeTracker缓存机制:

  • 测试类中注入的_rolesRepository使用的是同一个DbContext实例,调用CreateAsync添加角色后,该DbContext的ChangeTracker已缓存了该角色实体。
  • 调用API执行删除操作时,API控制器会使用新的DbContext实例(ASP.NET Core默认是作用域生命周期,每个请求创建一个DbContext),它从数据库查询角色并更新Deleted属性后保存到数据库。
  • 此时测试类的DbContext缓存里还是旧的角色实体(Deleted=false),调用GetByIdAsync时,EF Core直接从缓存返回该实体,不会去数据库读取最新数据,所以得到的Deleted值仍是false。
  • 通过API查询时,API的DbContext是新的,会从数据库读取最新数据,因此能得到正确的Deleted=true结果。

解决方案

以下几种方法可以解决这个问题:

1. 清空测试DbContext的ChangeTracker缓存

在调用GetByIdAsync前,清空ChangeTracker,强制EF Core从数据库重新查询:

// 在查询前添加这行代码
_db.ChangeTracker.Clear();

var deletedRoleById = await _rolesRepository.GetByIdAsync(roleToDelete.Id);
deletedRoleById.Deleted.ShouldBeTrue();

2. 修改GetByIdAsync方法使用AsNoTracking

让查询跳过ChangeTracker缓存,每次都从数据库获取最新数据:

public override async Task<RoleModel> GetByIdAsync(Guid Id)
{
    var roleEntity = await _db.Roles
                         .AsNoTracking() // 添加这行
                         .Include(r => r.RoleFeatures)
                             .ThenInclude(rf => rf.Feature)
                         .FirstOrDefaultAsync(r => r.Id == Id);

    return _mapper.Map<RoleModel>(roleEntity);
}

注意:AsNoTracking会让实体脱离ChangeTracker管理,适合只读场景,如果后续需要修改该实体,需要重新附加到上下文。

3. 使用API查询代替仓库直接查询

就像你已经验证的那样,通过客户端调用API查询角色,确保使用新的DbContext实例获取最新数据。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 18:57:06