Moq - 不可覆盖的成员不得用于设置/验证表达式

Moq - Non-overridable members may not be used in setup / verification expressions

我是最小起订量的新手。我在嘲笑 PagingOptions class。这是 class 的样子:

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

这是我的模拟版本 class,

var mockPagingOptions = new Mock<PagingOptions>();
            mockPagingOptions.Setup(po => po.Limit).Returns(25);
            mockPagingOptions.Setup(po => po.Offset).Returns(0);

设置 属性 值时出现以下错误。我做错了什么吗?看起来我不能具体起订量 class?只能模拟接口吗?请协助。

谢谢, 阿卜杜勒

Moq 创建模拟类型的实现。如果类型是接口,它会创建一个 class 来实现该接口。如果类型是 class,它会创建一个继承的 class,并且继承的 class 的成员调用基类 class。但为了做到这一点,它必须覆盖成员。如果 class 具有无法覆盖的成员(它们不是虚拟的、抽象的),则 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 };

我们如何确定是否要模拟某些东西?一般来说,如果我们不想在测试中包含具体的运行时实现,我们就会模拟一些东西。我们想测试一个 class 而不是同时测试两个。

但在这种情况下 PagingOptions 只是一个保存一些数据的 class。嘲笑它真的没有意义。使用真实的东西也一样容易。

我有同样的错误,但在我的例子中,我试图模拟 class 本身而不是它的接口:

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

我想改进Scott's答案并给出一般答案

If the type is a class, it creates an inherited class, and the members of that inherited class call the base class. But in order to do that it has to override the members. If a class has members that can't be overridden (they aren't virtual, abstract) then Moq can't override them to add its own behaviors.

在我的情况下,我不得不将道具虚拟化。因此,您的 class 代码的答案是:

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

如果你根据原标题Non-overridable members may not be used in setup / verification expressions和none的其他答案得到了帮助,你可能想看看反射是否能满足你的测试需求。

假设您有一个 class Foo,其中 属性 定义为 public int I { get; private set; }

如果您尝试此处答案中的各种方法,其中很少有方法适用于这种情况。但是,您可以使用 .net 反射来设置实例变量的值,并且仍然在代码中保持相当好的重构支持。

这是设置 属性 和 private setter:

的片段
var foo = new Foo();
var I = foo.GetType().GetProperty(nameof(Foo.I), BindingFlags.Public | BindingFlags.Instance);
I.SetValue(foo, 8675309);

我不建议将此用于生产代码。它在我的无数测试中被证明非常有用。我几年前发现了这种方法,但最近需要再次查找,这是最热门的搜索结果。