Moq - 不可重写的成员不得在设置/验证表达式中使用

问题描述 投票:0回答:7

我是最小起订量新手。我正在嘲笑一个

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

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

谢谢, 阿卜杜勒

c# .net unit-testing moq
7个回答
246
投票

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
只是一个保存一些数据的类。实在是没有必要嘲笑它。使用真实的东西一样容易。


83
投票

我有同样的错误,但就我而言,我试图模拟类本身而不是它的接口:

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

16
投票

如果您根据原始标题

Non-overridable members may not be used in setup / verification expressions
提出了这个问题,并且其他答案都没有帮助您,您可能想看看反射是否可以满足您的测试需求。

假设您有一个类

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

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


14
投票

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

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

2
投票

就我而言,我正在嘲笑一个非虚拟的公共方法。使方法变得虚拟就成功了。

作为一名老Java开发人员,我习惯了所有公共方法都已经是虚拟的方法,无需将它们单独标记为虚拟以便子类可以覆盖它们。 C# 在这里有所不同。

也许有人可以解释是否可以将生产代码中的公共方法标记为虚拟以用于 C# 中的测试目的。


1
投票

有时,您可能正在使用第三方库中的类,该类具有可以直接设置或模拟的属性。

如果上面的答案不够,您还可以直接调用 setter 方法,例如,类有一个名为“Id”的属性,但没有可访问的 setter:

var idSetter = account.GetType().GetMethod("set_Id", BindingFlags.Instance | BindingFlags.NonPublic);

idSetter!.Invoke(account, new[] { "New ID Here" });

1
投票

聚会迟到了,但我为那些还饿的人带来了一些美味的蛋糕。 我发现有一个特定的 SetupGet 允许你 mock getters。它不需要在类方面更改任何代码。

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

还有一个 SetupSet 版本,负责处理 Setter。

© www.soinside.com 2019 - 2024. All rights reserved.