为什么收到消息“在非虚拟(在VB中可重写)成员上的无效设置...”消息,导致异常?


176

我有一个单元测试,必须模拟返回布尔类型的非虚拟方法

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什么?
Preston Guillot

9
它很简单..您需要标记它virtual。Moq无法模拟无法覆盖的具体类型。
西蒙·怀特海德

Answers:


265

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

我认为您应该考虑的另一件事是为“ CupboardAccess”引入一个接口,并开始模拟该接口。这将帮助您解耦代码,并从长远来看受益。

最后,还有像TypeMockJustMock这样的框架,它们可以直接与IL配合使用,因此可以模拟非虚拟方法。但是,两者都是商业产品。


59
您只应模拟接口这一事实为+1。这个问题解决了我遇到的问题,因为我不小心嘲笑了类,而不是底层的接口。
保罗·拉夫

1
这不仅解决了问题,而且对所有需要测试的类都使用接口是一个很好的实践。Moq本质上迫使您拥有良好的Dependency Inversion,而其他一些嘲笑框架也使您可以绕开该原理。
Xipooo

如果我的接口(例如IPeopleRepository)具有伪造的实现(例如FakePeopleRepository),并且我在模仿伪造的实现,是否会违反该原则?我认为仍保留了IoC,因为在测试设置中,我必须将伪对象传递给服务类,该服务类在其构造函数中使用接口。
paz

1
@paz使用最小起订量的全部目的是避免伪造的实现。现在考虑检查边界条件等需要多少个伪实现的变体。理论上,是的,您可以模拟一个伪实现。但是实际上,这听起来像是代码的味道。
Amol

请注意,接口上的扩展方法实际上可能会发生此错误,这可能会造成混淆。
Dan Pantry'7

34

作为对与我有相同问题的任何人的帮助,我无意中输入了实现类型而不是接口类型,例如

var mockFileBrowser = new Mock<FileBrowser>();

代替

var mockFileBrowser = new Mock<IFileBrowser>();

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.