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
相关产品推荐
相关产品推荐

