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

设置属性值时出现以下错误。我是不是做错了什么。看起来我不能起订量具体课程?只有接口可以被模拟?请帮忙。

谢谢, 阿卜杜勒

【问题讨论】:

将属性 OffsetLimit 设为虚拟。 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 - 不可覆盖的成员不能用于设置/验证表达式的主要内容,如果未能解决你的问题,请参考以下文章

在 Moq 中重置模拟验证?

如何使用 Moq 为不同的参数设置两次方法

四月二十日java基础知识

使用 Moq 验证特定参数

Moq 使用对象参数验证

如何验证在 Moq 中没有调用该方法?