为什么我得到一个异常消息“非虚拟(在 VB 中可重写)成员上的无效设置...”?

我有一个单元测试,其中我必须模拟一个返回 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>())

有什么建议可以绕过这个异常吗?

171768 次浏览

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

我认为您应该考虑的另一件事情是为您的“ CupboardAccess”引入一个接口,并开始模仿该接口。它将帮助您解耦代码,并且在长期运行中有好处。

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

请参阅 为什么我想要模拟的属性需要是虚拟的?

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

密码:

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

但看到例外。

为了帮助那些和我有同样问题的人,我不小心输错了实现类型而不是接口类型。

var mockFileBrowser = new Mock<FileBrowser>();

而不是

var mockFileBrowser = new Mock<IFileBrowser>();

如果验证调用了接口的扩展方法,也会出现这个错误。

例如,如果你在嘲笑:

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

不要模仿具体的类,你应该模仿那个类接口。 从 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.16的 Moq 版本,并且使用 .Result语法来模拟只支持从 Moq 4.16开始的异步方法

在模拟版本小于4.16下面的结果进入 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 上的 异步方法 Moq Wiki