如何验证Moq方法调用时引用类型参数的真实取值?
问题:验证方法调用时的引用类型参数状态,避免测试误过
先看背景代码:
正确的业务代码:
public class ExampleClass { public void ExampleMethod(IMyInterface myInterface) { var test = new DataClass(); test.Id = 5; myInterface.DoSomething(test); } } public class DataClass { public int Id {get;set;} }
用来验证的测试代码:
[TestMethod] public void Test() { var mock = new Mock<IMyInterface>(); var example = new ExampleClass(); example.ExampleMethod(mock.Object); mock.Verify(x => x.DoSomething(It.Is<DataClass>(y => y.Id == 5))); }
当业务代码出现bug——先调用DoSomething再给Id赋值:
public void ExampleMethod(IMyInterface myInterface) { var test = new DataClass(); myInterface.DoSomething(test); test.Id = 5; }
此时测试本该失败,但因为DataClass是引用类型,Verify执行时Id已经被改成5,导致测试误通过。需要让测试能正确识别这个bug。
解决办法
1. 用Callback捕获调用瞬间的参数状态
在Mock的方法上注册Callback,在DoSomething被调用的瞬间就把当时的Id值存下来,后续直接断言这个保存的值,不受后续修改影响。
[TestMethod] public void Test() { int capturedId = -1; var mock = new Mock<IMyInterface>(); // 注册回调,捕获调用时的Id mock.Setup(x => x.DoSomething(It.IsAny<DataClass>())) .Callback<DataClass>(data => capturedId = data.Id); var example = new ExampleClass(); example.ExampleMethod(mock.Object); // 先确认方法确实被调用过,再断言捕获到的Id mock.Verify(x => x.DoSomething(It.IsAny<DataClass>()), Times.Once); Assert.AreEqual(5, capturedId); }
这样在bug场景下,capturedId会是0(默认值),断言失败,测试就能正确识别问题。
2. 把DataClass改成值类型(业务允许的话)
值类型在传递时是复制副本,DoSomething拿到的是调用时的独立副本,后续修改原始实例的Id不会影响这个副本。
// 将DataClass改为struct值类型 public struct DataClass { public int Id { get; set; } }
此时原测试代码就能正确失败,因为调用DoSomething时传入的副本Id是0,后续修改的是另一个实例的Id,Verify检查的是调用时的副本状态。
3. 使用不可变类型(推荐方案)
让DataClass变成不可变,构造时就初始化Id,后续无法修改,从根源上避免后续修改参数状态的问题。
public class DataClass { public int Id { get; } // 只读属性,只能在构造时赋值 public DataClass(int id) { Id = id; } } // 修正后的业务代码写法 public void ExampleMethod(IMyInterface myInterface) { var test = new DataClass(5); // 构造时直接初始化Id myInterface.DoSomething(test); }
这种情况下,bug代码(先调用方法再赋值)会直接编译不通过,因为Id是只读的,必须在构造时设置。如果有其他类似的状态修改操作,也无法改变实例状态,测试时Verify检查的就是调用时的真实状态。
内容的提问来源于stack exchange,提问作者SomeBody
相关产品推荐
相关产品推荐

