为啥我会收到一条异常消息“非虚拟(在 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&lt;T&gt; 接口上的扩展。

public static void ValidateAndThrow&lt;T&gt;(this IValidator&lt;T&gt; 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 方法中收到此异常?