如何为「通过注入接口方法更新实体类属性」创建Moq模拟
问题与解决方案
场景代码
定义的接口
internal interface ITableAdapter { int CustomFill(DataSet dataset, ...); } internal interface IEntityRetriever { IEntity RetriveEntity(); }
实现类
internal class RetriveEntityById: IEntityRetriever { private readonly EntityDataSet _entityDataSet = new EntityDataSet(); private readonly ITableAdapter _tableAdapter; public RetriveEntityById(ITableAdapter tableAdapter, ...) { _tableAdapter = tableAdapter; ... } public Entity RetriveEntity() { var EntityModel = RetriveEntityModel(); ... return EntityModel.Entity; } private EntityDataSet RetriveEntityModel() { _tableAdapter.CustomFill(_entityDataSet, ...); return _entityDataSet; } }
测试困境
尝试对RetriveEntityById做单元测试时,不知道如何Mock CustomFill方法填充私有属性_entityDataSet的行为,当前Mock代码如下:
var adapter = new Mock<ITableAdapter>(); adapter.Setup(x=>x.CustomFill(It.IsAny<DataSet>(), ...)).Returns(1);
解决方法
方法1:利用Moq的Callback回调填充数据
不需要修改原代码,在Mock的Setup中添加回调逻辑,直接操作传入的EntityDataSet实例:
var adapter = new Mock<ITableAdapter>(); adapter.Setup(x => x.CustomFill(It.IsAny<EntityDataSet>(), ...)) .Returns(1) .Callback((EntityDataSet ds, /* 对应方法的其他参数 */) => { // 模拟填充DataSet的逻辑,根据实际结构添加数据 var entityRow = ds.Entity.NewEntityRow(); entityRow.Id = 1; entityRow.Name = "测试实体"; // 给其他必要字段赋值... ds.Entity.AddEntityRow(entityRow); });
当RetriveEntityModel调用CustomFill时,回调会直接修改类内部的私有_entityDataSet,后续业务代码就能拿到填充好的数据。
方法2:重构类,注入EntityDataSet(推荐长期维护)
修改原类的构造函数,将_entityDataSet改为依赖注入的方式,这样测试时可以自主控制DataSet实例:
internal class RetriveEntityById: IEntityRetriever { private readonly EntityDataSet _entityDataSet; private readonly ITableAdapter _tableAdapter; // 构造函数注入EntityDataSet public RetriveEntityById(ITableAdapter tableAdapter, EntityDataSet entityDataSet, ...) { _tableAdapter = tableAdapter; _entityDataSet = entityDataSet; ... } // 其余业务代码保持不变 }
对应的测试代码:
// 手动创建测试用的DataSet,可提前填充或后续操作 var testDataSet = new EntityDataSet(); var adapter = new Mock<ITableAdapter>(); // 初始化被测对象,传入自定义的DataSet var sut = new RetriveEntityById(adapter.Object, testDataSet, ...); // 如需模拟填充,也可以结合Callback操作testDataSet adapter.Setup(x => x.CustomFill(testDataSet, ...)) .Returns(1) .Callback(() => { // 填充testDataSet的逻辑 });
这种方式符合依赖注入原则,让类的可测试性更强,也便于后续扩展。
方法3:反射访问私有字段(不推荐)
通过反射强行获取并修改私有_entityDataSet,但这种方式破坏封装,代码脆弱,仅作为临时方案:
var adapter = new Mock<ITableAdapter>(); var sut = new RetriveEntityById(adapter.Object, ...); // 通过反射获取私有字段 var dataSetField = typeof(RetriveEntityById).GetField( "_entityDataSet", BindingFlags.NonPublic | BindingFlags.Instance ); var entityDataSet = (EntityDataSet)dataSetField.GetValue(sut); // 手动填充数据 var entityRow = entityDataSet.Entity.NewEntityRow(); entityRow.Id = 1; // 赋值其他字段... entityDataSet.Entity.AddEntityRow(entityRow);
内容的提问来源于stack exchange,提问作者Uday
相关产品推荐
相关产品推荐

