【问题标题】: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 无法模拟它无法覆盖的具体类型。

标签: c# unit-testing moq


【解决方案1】:

Moq 不能模拟非虚拟方法和密封类。在使用模拟对象运行测试时,MOQ 实际上创建了一个内存代理类型,它继承自您的“XmlCupboardAccess”并覆盖您在“SetUp”方法中设置的行为。正如您在 C# 中所知道的那样,只有当它被标记为虚拟时,您才能覆盖某些东西,而 Java 不是这种情况。 Java 假定每个非静态方法默认都是虚拟的。

我认为您应该考虑的另一件事是为您的“CupboardAccess”引入一个界面并开始模拟该界面。它将帮助您解耦代码并从长远来看有好处。

最后,有像 TypeMockJustMock 这样的框架,它们直接与 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);
            }        
            

            但请参阅异常。

            【讨论】:

            • 您能解释一下您的解决方案吗?此外,“见异常......”悬而未决。你能扩展一下吗?
            猜你喜欢
            • 2015-09-05
            • 2017-05-12
            • 2014-07-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-09-02
            • 1970-01-01
            相关资源
            最近更新 更多