为啥我会收到一条异常消息“非虚拟(在 VB 中可覆盖)成员上的设置无效...”?
Posted
技术标签:
【中文标题】为啥我会收到一条异常消息“非虚拟(在 VB 中可覆盖)成员上的设置无效...”?【英文标题】:Why am I getting an Exception with the message "Invalid setup on a non-virtual (overridable in VB) member..."?为什么我会收到一条异常消息“非虚拟(在 VB 中可覆盖)成员上的设置无效...”? 【发布时间】:2014-03-13 04:32:00 【问题描述】:我有一个单元测试,我必须模拟一个返回 bool 类型的非虚拟方法
public class XmlCupboardAccess
public bool IsDataEntityInXmlCupboard(string dataId,
out string nameInCupboard,
out string refTypeInCupboard,
string nameTemplate = null)
return IsDataEntityInXmlCupboard(_theDb, dataId, out nameInCupboard, out refTypeInCupboard, nameTemplate);
所以我有一个 XmlCupboardAccess
类的模拟对象,我试图在我的测试用例中为这个方法设置模拟,如下所示
[TestMethod]
Public void Test()
private string temp1;
private string temp2;
private Mock<XmlCupboardAccess> _xmlCupboardAccess = new Mock<XmlCupboardAccess>();
_xmlCupboardAccess.Setup(x => x.IsDataEntityInXmlCupboard(It.IsAny<string>(), out temp1, out temp2, It.IsAny<string>())).Returns(false);
//exception is thrown by this line of code
但是这一行抛出异常
Invalid setup on a non-virtual (overridable in VB) member:
x => x.IsDataEntityInXmlCupboard(It.IsAny<String>(), .temp1, .temp2,
It.IsAny<String>())
有什么建议可以解决这个异常吗?
【问题讨论】:
你的测试取决于XmlCupboardAccess
?
很简单..你需要标记它virtual
。 Moq 无法模拟它无法覆盖的具体类型。
【参考方案1】:
Moq 不能模拟非虚拟方法和密封类。在使用模拟对象运行测试时,MOQ 实际上创建了一个内存代理类型,它继承自您的“XmlCupboardAccess”并覆盖您在“SetUp”方法中设置的行为。正如您在 C# 中所知道的那样,只有当它被标记为虚拟时,您才能覆盖某些东西,而 Java 不是这种情况。 Java 假定每个非静态方法默认都是虚拟的。
我认为您应该考虑的另一件事是为您的“CupboardAccess”引入一个界面并开始模拟该界面。它将帮助您解耦代码并从长远来看有好处。
最后,有像 TypeMock 和 JustMock 这样的框架,它们直接与 IL 一起工作,因此可以模拟非虚拟方法。然而,两者都是商业产品。
【讨论】:
+1 关于你应该只模拟接口这一事实。这个问题解决了我遇到的问题,因为我不小心模拟了类而不是底层接口。 这不仅解决了问题,而且为所有需要测试的类使用接口是一种很好的做法。 Moq 本质上是在强迫您拥有良好的依赖倒置,而其他一些模拟框架允许您绕过这一原则。 如果我的接口(例如 IPeopleRepository)有一个虚假的实现(例如 FakePeopleRepository),并且我在嘲笑这个虚假的实现,是否会被视为违反此原则?我认为 IoC 仍然保留,因为在我的测试设置中,我必须将假对象传递给我的服务类,该类在其构造函数中采用接口。 @paz 使用 MOQ 的全部意义在于避免虚假实现。现在考虑检查边界条件等需要多少假实现的变体。理论上,是的,您可以模拟假实现。但实际上这听起来像是一种代码味道。 请注意,这个错误实际上可能发生在接口上的扩展方法上,这可能会造成混淆。【参考方案2】:为了帮助和我有同样问题的人,我不小心打错了实现类型而不是接口,例如
var mockFileBrowser = new Mock<FileBrowser>();
而不是
var mockFileBrowser = new Mock<IFileBrowser>();
【讨论】:
【参考方案3】:您应该模拟该类接口,而不是模拟具体类。 从 XmlCupboardAccess 类中提取接口
public interface IXmlCupboardAccess
bool IsDataEntityInXmlCupboard(string dataId, out string nameInCupboard, out string refTypeInCupboard, string nameTemplate = null);
而不是
private Mock<XmlCupboardAccess> _xmlCupboardAccess = new Mock<XmlCupboardAccess>();
改成
private Mock<IXmlCupboardAccess> _xmlCupboardAccess = new Mock<IXmlCupboardAccess>();
【讨论】:
【参考方案4】:请看 Why does the property I want to mock need to be virtual?
您可能必须编写一个包装接口或将属性标记为虚拟/抽象,因为 Moq 创建了一个代理类,用于拦截调用并返回您在 .Returns(x)
调用中放入的自定义值。
【讨论】:
【参考方案5】:如果您正在验证接口的扩展方法是否被调用,您也会收到此错误。
例如,如果你在嘲笑:
var mockValidator = new Mock<IValidator<Foo>>();
mockValidator
.Verify(validator => validator.ValidateAndThrow(foo, null));
您将得到同样的异常,因为.ValidateAndThrow()
是IValidator<T>
接口上的扩展。
public static void ValidateAndThrow<T>(this IValidator<T> validator, T instance, string ruleSet = null)...
【讨论】:
【参考方案6】:在我的例子中,我使用的是低于 4.16 的最小起订量版本,并且使用 .Result
语法来模拟仅从最小起订量 4.16 开始支持的异步方法
在低于 4.16 的模拟版本上,即使在使用接口时,也会将结果跟踪到 Invalid setup on a non-virtual member ...
。
mock.Setup(foo => foo.DoSomethingAsync().Result).Returns(true);
在低于 4.16 的最小起订量版本上使用以下
mock.Setup(foo => foo.DoSomethingAsync()).ReturnsAsync(true);
有关更多信息,请参阅 Github 上的 Async Methods Moq Wiki
【讨论】:
【参考方案7】:代码:
private static void RegisterServices(IKernel kernel)
Mock<IProductRepository> mock=new Mock<IProductRepository>();
mock.Setup(x => x.Products).Returns(new List<Product>
new Product Name = "Football", Price = 23,
new Product Name = "Surf board", Price = 179,
new Product Name = "Running shose", Price = 95
);
kernel.Bind<IProductRepository>().ToConstant(mock.Object);
但请参阅异常。
【讨论】:
您能解释一下您的解决方案吗?此外,“见异常......”悬而未决。你能扩展一下吗?以上是关于为啥我会收到一条异常消息“非虚拟(在 VB 中可覆盖)成员上的设置无效...”?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我会收到 50% 的 GCP Pub/Sub 消息重复?
当 Spring Cloud Stream 反应式使用者遇到异常时,为啥我会收到 onComplete 信号?
为啥我会收到“服务未注册”异常,即使我没有在 Android - Java/Kotlin 中使用任何服务?
“在 onSaveInstanceState 之后无法执行此操作” - 为啥我会从我的活动的 onResume 方法中收到此异常?