为什么我收到消息“非虚拟(在VB中可覆盖)成员上的无效设置...”?

时间:2014-02-14 00:47:36

标签: c# unit-testing moq

我有一个单元测试,我必须模拟一个返回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类的模拟对象,我试图在我的测试用例中为这个方法设置mock,如下所示

[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>())

有关如何解决此异常的任何建议吗?

7 个答案:

答案 0 :(得分:238)

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

我认为你应该考虑的另一件事是为你的“CupboardAccess”引入一个界面,然后开始嘲笑界面。它可以帮助您解耦代码,并在较长时间内获益。

最后,有一些框架如:TypeMockJustMock直接与IL协同工作,因此可以模拟非虚方法。然而,两者都是商业产品。

答案 1 :(得分:18)

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

var mockFileBrowser = new Mock<FileBrowser>();

代替

var mockFileBrowser = new Mock<IFileBrowser>();

答案 2 :(得分:4)

请看 Why does the property I want to mock need to be virtual?

您可能必须编写包装器接口或将属性标记为虚拟/抽象,因为Moq创建了一个代理类,用于拦截调用并返回您在.Returns(x)调用中放置的自定义值。

答案 3 :(得分:1)

就我而言,我使用的是低于 4.16 的 Moq 版本,并且使用 .Result 语法来模拟仅从 Moq 4.16 开始支持的异步方法

在低于 4.16 的模拟版本中,即使在使用 Interface 时也会导致 Invalid setup on a non-virtual member ...

mock.Setup(foo => foo.DoSomethingAsync().Result).Returns(true);

在低于 4.16 的 Moq 版本上使用以下

mock.Setup(foo => foo.DoSomethingAsync()).ReturnsAsync(true);

有关更多信息,请参阅 Github 上的 Async Methods Moq Wiki

答案 4 :(得分:0)

如果您正在验证是否调用了接口的扩展方法,也会收到此错误。

例如,如果您在嘲笑:

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)...

答案 5 :(得分:0)

您应该模拟该类接口,而不是模拟具体的类。 从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>();

答案 6 :(得分:-12)

代码:

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);
}        

但看到异常。