XUnit中使用Moq传入映射实体时单元测试失败的修复咨询
问题描述
在XUnit中使用Moq编写单元测试时,调用_sut.Create抛出异常:
Moq.MockException: 'IBaseCRUDRepository<client>.Create(client) invocation failed with mock behavior Strict. All invocations on the mock must have a corresponding setup.'
移除MockBehavior.Strict后,_sut.Create返回0而非预期的1。
测试代码
_logger = new Mock<ILogger<BaseCRUDDomainService<ClientDTO, client>>>().Object; var mockMapper = new MapperConfiguration(cfg => { cfg.AddProfile(new DomainMapper()); }); _mapper = mockMapper.CreateMapper(); var mockRepository = new Mock<IBaseCRUDRepository<client>>(MockBehavior.Strict); //期望返回1 var clientDTO = new ClientDTO { Name = "name", Description = "description", Address = "address", Billing = "billing", CellphoneNumber = "cellphone", Email = "email" }; mockRepository.Setup(r => r.Create(_mapper.Map<client>(clientDTO))) .Returns(1); var _baseCRUDRepository = mockRepository.Object; var _sut = new ClientCRUDDomainService(_logger, _mapper, _baseCRUDRepository); var response = _sut.Create(clientDTO);
待测试方法
public int Create(clientDTO entity) { var entityDB = _mapper.Map<clientDB>(entity); return _baseCRUDRepository.Create(entityDB); }
ClientDTO类
public class ClientDTO { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public string Billing { get; set; } public string Address { get; set; } public string Email { get; set; } public string CellphoneNumber { get; set; } public int[]? Users { get; set; } }
数据库实体client类
public partial class client { public int Id { get; set; } public string name { get; set; } = null!; public string? description { get; set; } public string? billing { get; set; } public string? address { get; set; } public string? email { get; set; } public string? cellphone_number { get; set; } }
映射器DomainMapper类
public class DomainMapper : Profile { public DomainMapper() : base() { var configuration = new MapperConfiguration(cfg => { cfg.AllowNullCollections = true; }); configuration.CompileMappings(); configuration.AssertConfigurationIsValid(); CreateMap<ClientDTO, client>() .ForMember(db => db.Id, opt => opt.MapFrom(dto => dto.Id)) .ForMember(db => db.cellphone_number, opt => opt.MapFrom(dto => dto.CellphoneNumber)) .ReverseMap() .ForMember(dto => dto.Id, opt => opt.MapFrom(src => src.Id)) .ForMember(dto => dto.CellphoneNumber, opt => opt.MapFrom(src => src.cellphone_number)); } }
问题原因
核心问题是Moq的默认匹配逻辑是引用相等:你在Setup时传入了_mapper.Map<client>(clientDTO)生成的具体对象实例,但在待测试方法Create内部,AutoMapper会重新生成一个全新的client对象实例。这两个实例属性值一致,但内存引用不同,Moq会判定为不匹配的调用,因此Strict模式下直接报错;非Strict模式下因无匹配的Setup,返回int类型的默认值0。
另外待测试方法存在笔误:_mapper.Map<clientDB>(entity)中的clientDB应该是client,否则映射会失败(你的数据库实体类是client)。
修复方案
方案1:匹配任意client实例(简单快捷)
如果不需要验证传入Create方法的实体属性,直接用It.IsAny<client>()匹配所有client类型的参数:
mockRepository.Setup(r => r.Create(It.IsAny<client>())) .Returns(1);
方案2:匹配指定属性的client实例(精准验证)
如果需要确保传入的实体属性符合预期,用It.Is<client>(x => ...)做属性匹配:
mockRepository.Setup(r => r.Create(It.Is<client>(x => x.name == clientDTO.Name && x.description == clientDTO.Description && x.address == clientDTO.Address && x.billing == clientDTO.Billing && x.cellphone_number == clientDTO.CellphoneNumber && x.email == clientDTO.Email ))) .Returns(1);
额外修复:修正待测试方法的映射类型
把待测试方法中的clientDB改为client,确保映射逻辑正确:
public int Create(ClientDTO entity) { var entityDB = _mapper.Map<client>(entity); // 修正clientDB为client return _baseCRUDRepository.Create(entityDB); }
内容的提问来源于stack exchange,提问作者Milton Calderon

