单元测试返回Null问题:FirstOrDefault谓词导致partProdArea为空
单元测试中AutoFixture+NSubstitute+Fluent Assertions遇到的FirstOrDefault空值问题
我在单元测试中使用AutoFixture、NSubstitute和Fluent Assertions。主代码的Update方法里,通过repository.GetQueryable().FirstOrDefault(predicate: q => q.Id == planInventoryDto.Id)获取的partProdArea为Null,导致测试返回错误结果。试了多种方法都没解决,想问下有没有其他编写带谓词的FirstOrDefault的方式?
主代码
public async Task<ServiceResponse<PlanInventoryDto>> Update(PlanInventoryDto planInventoryDto) { planInventoryDto.ThrowIfNull(nameof(planInventoryDto)); if (planInventoryDto == null) { return serviceResponseHelper.SetError<PlanInventoryDto>(null, localize["InvalidModel"], StatusCodes.Status400BadRequest, true); } var partProdArea = repository.GetQueryable().FirstOrDefault(predicate: q => q.Id == planInventoryDto.Id); if (partProdArea != null) { var mappingResult = mapper.Map(planInventoryDto, partProdArea); await repository.UpdateAsync(mappingResult).ConfigureAwait(false); var result = repository.GetFirstOrDefault(predicate: p => p.Id == planInventoryDto.Id); var dto = mapper.Map<PlanInventory, PlanInventoryDto>(result); return serviceResponseHelper.SetSuccess(dto); } return serviceResponseHelper.SetError<PlanInventoryDto>(null, localize["ModelIsNotFound"], StatusCodes.Status400BadRequest, true); }
单元测试代码
[Fact] [Trait("Operation", "Update")] [Trait("Category", "PlanInventory")] public async Task PlanInventoryService_Update_ReturnsSuccessResponse() { // Arrange var planInventoryDto = _fixture.Create<PlanInventoryDto>(); var planInventory = _fixture.Build<Domain.PlanProcess.PlanInventory>() .With(p => p.Id, planInventoryDto.Id) .Create(); var updatedPlanInventory = _fixture.Create<Domain.PlanProcess.PlanInventory>(); var updatedDto = _fixture.Create<PlanInventoryDto>(); _repository.GetFirstOrDefault(Arg.Any<Expression<Func<Domain.PlanProcess.PlanInventory, bool>>>(), Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IOrderedQueryable<Domain.PlanProcess.PlanInventory>>>(), Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IIncludableQueryable<Domain.PlanProcess.PlanInventory, object>>>()) .Returns(planInventory); _repository.GetFirstOrDefault(Arg.Any<Expression<Func<Domain.PlanProcess.PlanInventory, bool>>>(), Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IOrderedQueryable<Domain.PlanProcess.PlanInventory>>>(), Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IIncludableQueryable<Domain.PlanProcess.PlanInventory, object>>>()) .Returns(updatedPlanInventory); _mapper.Map(Arg.Any<PlanInventoryDto>(), Arg.Any<Domain.PlanProcess.PlanInventory>()) .Returns(updatedPlanInventory); _repository.UpdateAsync(Arg.Any<Domain.PlanProcess.PlanInventory>()) .Returns(Task.FromResult(updatedPlanInventory)); _mapper.Map<Domain.PlanProcess.PlanInventory, PlanInventoryDto>(updatedPlanInventory) .Returns(updatedDto); // Act var result = await _planInventoryService.Update(planInventoryDto); // Assert _serviceResponseHelper.Received(1).SetSuccess(Arg.Any<PlanInventoryDto>()); }
解决方法
1. 修复单元测试的Mock逻辑
问题核心是你只Mock了GetFirstOrDefault方法,但主代码里调用的是GetQueryable().FirstOrDefault(...),这个调用链没有被Mock,导致GetQueryable()返回空的IQueryable,最终FirstOrDefault返回null。
需要给GetQueryable()添加Mock,让它返回包含目标实体的查询集合:
// 在Arrange部分添加这行代码 _repository.GetQueryable().Returns(new List<Domain.PlanProcess.PlanInventory> { planInventory }.AsQueryable());
2. 优化主代码的查询写法(可选)
如果你的仓储已经提供了带谓词的GetFirstOrDefault方法,直接用它替代链式调用,减少依赖:
// 替换原来的GetQueryable().FirstOrDefault(...) var partProdArea = repository.GetFirstOrDefault(predicate: q => q.Id == planInventoryDto.Id);
或者简化FirstOrDefault的写法(和原逻辑等价,但更简洁):
var partProdArea = repository.GetQueryable().FirstOrDefault(q => q.Id == planInventoryDto.Id);
3. 修正重复Mock的问题
你两次Mock了GetFirstOrDefault,第二次会覆盖第一次的返回值,应该用序列返回的方式来模拟两次调用:
// 替换原来的两个GetFirstOrDefault Mock _repository.GetFirstOrDefault(Arg.Any<Expression<Func<Domain.PlanProcess.PlanInventory, bool>>>(), Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IOrderedQueryable<Domain.PlanProcess.PlanInventory>>>(), Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IIncludableQueryable<Domain.PlanProcess.PlanInventory, object>>>()) .ReturnsNextFromSequence(planInventory, updatedPlanInventory);
内容的提问来源于stack exchange,提问作者sedya
相关产品推荐
相关产品推荐

