如何Mock被测系统(SUT)中调用的构造函数?
解决方案:通过抽取实体创建逻辑实现隔离测试
重构思路
核心问题是当前服务直接new DataEntity(dto),导致测试时必须依赖合法的DTO实例。解决办法是把DataEntity的创建逻辑抽成独立的依赖,让服务通过接口调用创建逻辑,而非直接实例化。
步骤1:定义实体工厂接口
新增一个专门负责从DTO生成DataEntity的接口,把构造逻辑移到这个接口的实现中:
public interface IDataEntityFactory { DataEntity Create(Dto dto); }
步骤2:实现工厂类
这个类只负责封装DataEntity的构造逻辑,因为构造函数已经有独立测试,这里不需要额外逻辑:
public class DataEntityFactory : IDataEntityFactory { public DataEntity Create(Dto dto) { return new DataEntity(dto); } }
步骤3:修改原服务代码
在服务中注入IDataEntityFactory,替换掉直接new的逻辑:
// 服务构造函数新增工厂注入 private readonly IInjectedDtoBuilder _injectedDtoBuilder; private readonly IInjectedRepository _injectedRepository; private readonly IDataEntityFactory _entityFactory; public YourService(IInjectedDtoBuilder injectedDtoBuilder, IInjectedRepository injectedRepository, IDataEntityFactory entityFactory) { _injectedDtoBuilder = injectedDtoBuilder; _injectedRepository = injectedRepository; _entityFactory = entityFactory; } public async Task ExecuteAsync() { IEnumerable<Dto> dtos = await _injectedDtoBuilder.CreateAsync(_injectedParameters); foreach (Dto dto in dtos) { // 用工厂创建实体,而非直接new DataEntity dataEntity = _entityFactory.Create(dto); await _injectedRepository.AddAsync(dataEntity); } }
单元测试方案
现在可以完全隔离DataEntity的构造逻辑,通过Moq Mock工厂,返回预设的实体实例,无需构造完整DTO:
[Test] public async Task ExecuteAsync_ProcessesAllDtos_AndCallsRepositoryAdd() { // 准备测试数据:随便造几个DTO,不用填充完整字段 var testDtos = new List<Dto> { new Dto(), new Dto() }; var testEntities = testDtos.Select(_ => new DataEntity(new Dto())).ToList(); // Mock各依赖 var mockDtoBuilder = new Mock<IInjectedDtoBuilder>(); mockDtoBuilder.Setup(b => b.CreateAsync(It.IsAny<YourParameterType>())) .ReturnsAsync(testDtos); var mockEntityFactory = new Mock<IDataEntityFactory>(); for (int i = 0; i < testDtos.Count; i++) { mockEntityFactory.Setup(f => f.Create(testDtos[i])) .Returns(testEntities[i]); } var mockRepository = new Mock<IInjectedRepository>(); // 实例化服务 var service = new YourService(mockDtoBuilder.Object, mockRepository.Object, mockEntityFactory.Object); // 执行测试方法 await service.ExecuteAsync(); // 验证每个实体都被正确传入Repository的AddAsync foreach (var entity in testEntities) { mockRepository.Verify(r => r.AddAsync(entity, It.IsAny<CancellationToken>()), Times.Once, "每个实体都应该被调用一次AddAsync"); } }
可选简化方案:用委托替代接口
如果觉得新增接口太繁琐,也可以用Func<Dto, DataEntity>作为依赖注入到服务中,代码更轻量:
// 服务构造函数改为注入委托 public YourService(IInjectedDtoBuilder injectedDtoBuilder, IInjectedRepository injectedRepository, Func<Dto, DataEntity> entityCreator) { _injectedDtoBuilder = injectedDtoBuilder; _injectedRepository = injectedRepository; _entityCreator = entityCreator; } // 执行方法中调用委托创建实体 DataEntity dataEntity = _entityCreator(dto);
测试时直接传入一个返回预设实体的委托即可,不需要Mock接口:
var testEntity = new DataEntity(new Dto()); Func<Dto, DataEntity> mockCreator = _ => testEntity;
内容的提问来源于stack exchange,提问作者Tam Coton
相关产品推荐
相关产品推荐

