Moq - 不可覆盖的成员不能用于设置/验证表达式
Posted
技术标签:
【中文标题】Moq - 不可覆盖的成员不能用于设置/验证表达式【英文标题】:Moq - Non-overridable members may not be used in setup / verification expressions 【发布时间】:2019-11-16 05:40:56 【问题描述】:我是 Moq 的新手。我在嘲笑 PagingOptions
类。下面是这个类的样子:
public class PagingOptions
[Range(1, 99999, ErrorMessage = "Offset must be greater than 0.")]
public int? Offset get; set;
[Range(1, 100, ErrorMessage = "Limit must be greater than 0 and less than 100.")]
public int? Limit get; set;
public PagingOptions Replace(PagingOptions newer)
return new PagingOptions
Offset = newer.Offset ?? Offset,
Limit = newer.Limit ?? Limit
;
这是我的模拟版课程,
var mockPagingOptions = new Mock<PagingOptions>();
mockPagingOptions.Setup(po => po.Limit).Returns(25);
mockPagingOptions.Setup(po => po.Offset).Returns(0);
设置属性值时出现以下错误。我是不是做错了什么。看起来我不能起订量具体课程?只有接口可以被模拟?请帮忙。
谢谢, 阿卜杜勒
【问题讨论】:
将属性Offset
和 Limit
设为虚拟。 docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
在这种情况下,似乎没有理由嘲笑它。您可以只创建PagingOptions
的实际实例并设置其属性,而不是使用Mock
。不要让任何东西变得虚拟。
除非这是XY problem,否则我认为没有必要模拟这个对象。使用该对象似乎没有明显的影响,这将保证必须创建一个模拟
@ScottHannen 如何找到要模拟和不模拟的对象?你是如何区分的?
@AbdulRahman ***.com/a/38256/5233410
【参考方案1】:
Moq 创建模拟类型的实现。如果类型是接口,它会创建一个实现该接口的类。如果类型是类,它会创建一个继承类,并且该继承类的成员调用基类。但为了做到这一点,它必须覆盖成员。如果一个类的成员不能被覆盖(它们不是虚拟的、抽象的),那么 Moq 就不能覆盖它们来添加自己的行为。
在这种情况下,无需模拟 PagingOptions
,因为它很容易使用真实的。而不是这个:
var mockPagingOptions = new Mock<PagingOptions>();
mockPagingOptions.Setup(po => po.Limit).Returns(25);
mockPagingOptions.Setup(po => po.Offset).Returns(0);
这样做:
var pagingOptions = new PagingOptions Limit = 25, Offset = 0 ;
我们如何确定是否要模拟某些东西?一般来说,如果我们不想在测试中包含具体的运行时实现,我们会模拟一些东西。我们想测试一个类而不是同时测试两个类。
但在这种情况下,PagingOptions
只是一个包含一些数据的类。嘲笑它真的没有意义。使用真实的东西同样容易。
【讨论】:
虽然如果这些是您要测试的唯一属性/方法,则此方法有效,但如果您想预测 pagingOptions 中的其他属性/方法行为,则无效。【参考方案2】:我有同样的错误,但在我的例子中,我试图模拟类本身而不是它的接口:
// Mock<SendMailBLL> sendMailBLLMock = new Mock<SendMailBLL>(); // Wrong, causes error.
Mock<ISendMailBLL> sendMailBLLMock = new Mock<ISendMailBLL>(); // This works.
sendMailBLLMock.Setup(x =>
x.InsertEmailLog(
It.IsAny<List<EmailRecipient>>(),
It.IsAny<List<EmailAttachment>>(),
It.IsAny<string>()));
【讨论】:
这很有帮助。太感谢了。如果你想设置一些操作,obj.Setup(...).Returns(/*mock implementation*/);
ref:***.com/questions/41351543/…
看起来很愚蠢,这也是我的问题。在深入挖掘可能出错的地方之前,请仔细检查您是否正在使用该界面。
我遇到了同样的问题,非常感谢您的宝贵回答。它适用于我,而是使用接口,就像你在代码块中提到的那样。大拇指支持。
这应该是实际接受的答案。在单元测试中模拟时使用接口被认为是一种最佳实践,以便可以灵活地删除和替换部分。此外,Moq 在幕后所做的事情也很有意义,它创建了一个适合定义的接口的类并重新定义了不同类成员的行为。【参考方案3】:
我想改进Scott's的答案并给出一个一般的答案
如果类型是一个类,它会创建一个继承类,并且该继承类的成员调用基类。但为了做到这一点,它必须覆盖成员。如果一个类的成员不能被覆盖(它们不是虚拟的、抽象的),那么 Moq 就不能覆盖它们来添加自己的行为。
在我的情况下,我不得不将道具设为虚拟。所以对你的课程代码的回答是:
public class PagingOptions
[Range (1, 99999, ErrorMessage = "Offset must be greater than 0.")]
public virtual int? Offset get; set;
[Range (1, 100, ErrorMessage = "Limit must be greater than 0 and less than 100.")]
public virtual int? Limit get; set;
public PagingOptions Replace (PagingOptions newer)
return new PagingOptions
Offset = newer.Offset ?? Offset,
Limit = newer.Limit ?? Limit
;
使用相同的:
var mockPagingOptions = new Mock<PagingOptions>();
mockPagingOptions.Setup(po => po.Limit).Returns(25);
mockPagingOptions.Setup(po => po.Offset).Returns(0);
【讨论】:
是的,但现在您正在修改属性访问器只是为了可测试性 是的,但如果你想模拟它,你必须这样做 应该注意官方 MS 文档将 DbSet 属性标记为虚拟用于此特定目的:docs.microsoft.com/en-us/ef/ef6/fundamentals/testing/…【参考方案4】:如果您根据原始标题 Non-overridable members may not be used in setup / verification expressions
提出了这个问题,而其他答案都没有帮助,您可能想看看反射是否可以满足您的测试需求。
假设您有一个类Foo
,其属性定义为public int I get; private set;
如果您尝试此处答案中的各种方法,其中很少有适用于这种情况的方法。但是,您可以使用 .net 反射来设置实例变量的值,并且仍然在代码中保持相当好的重构支持。
这是一个使用 private 设置器设置属性的 sn-p:
var foo = new Foo();
var I = foo.GetType().GetProperty(nameof(Foo.I), BindingFlags.Public | BindingFlags.Instance);
I.SetValue(foo, 8675309);
我不建议将此用于生产代码。它在我的许多测试中被证明非常有用。几年前我发现了这种方法,但最近需要再次查找,这是最热门的搜索结果。
【讨论】:
以上是关于Moq - 不可覆盖的成员不能用于设置/验证表达式的主要内容,如果未能解决你的问题,请参考以下文章