使用NSubstitute模拟带私有Setter的属性遇异常,求解决方案
你遇到的这个错误,核心原因是NSubstitute无法拦截非虚拟的类成员,而你的MyProperty1是普通的非虚拟自动属性,NSubstitute没法替换它的get访问逻辑,所以Returns方法找不到可绑定的调用,就抛出了CouldNotSetReturnDueToNoLastCallException。
下面给你几种可行的解决方案:
方案1:修改原类,将属性改为虚拟(推荐,如果有权限修改原代码)
NSubstitute只能模拟接口、抽象类,或者类中的虚拟/抽象成员。把MyProperty1改成虚拟属性后,NSubstitute就能拦截它的get操作并配置返回值了:
public class MyClass { // 添加virtual关键字 public virtual int MyProperty1 { get; private set; } public int MyProperty2 { get; private set; } public MyClass(){} public MyClass(int myProperty2) => MyProperty2 = myProperty2; }
修改后你的测试代码就能正常运行,fake.MyProperty1.Returns(5)会正确覆盖属性的返回值。
方案2:用反射绕过私有set设置属性值(如果不能修改原类)
因为MyProperty1有private set,我们可以通过反射直接调用它的私有set方法,或者修改自动属性对应的后台字段来设置值:
方式A:调用私有set方法
[Fact] public void MyTest() { var myObj = new MyClass(3); // 获取MyProperty1的属性信息 var propInfo = typeof(MyClass).GetProperty( nameof(MyClass.MyProperty1), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance ); // 获取私有set方法 var setter = propInfo.GetSetMethod(nonPublic: true); // 调用set方法设置值为5 setter.Invoke(myObj, new object[] { 5 }); // 执行断言 Assert.Equal(5, myObj.MyProperty1); }
方式B:直接修改自动属性的后台字段
对于C#的自动属性,编译器会生成一个名为_<PropertyName>的私有后台字段(比如MyProperty1对应_MyProperty1),我们可以直接修改这个字段的值:
[Fact] public void MyTest() { var myObj = new MyClass(3); // 获取后台字段 var fieldInfo = typeof(MyClass).GetField( "_MyProperty1", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance ); // 设置字段值 fieldInfo.SetValue(myObj, 5); // 执行断言 Assert.Equal(5, myObj.MyProperty1); }
为什么你的原测试代码会失败?
你用NSubstitute.Substitute.For<MyClass>(3)创建的是MyClass的部分模拟对象,但对于非虚拟的MyProperty1,NSubstitute无法替换它的get逻辑,调用fake.MyProperty1时还是会执行原类的get操作(返回默认值0),而Returns方法需要先捕获到一个可模拟的调用才能配置返回值,所以就抛出了找不到调用的异常。
内容的提问来源于stack exchange,提问作者Chitova263

