.net Moq -不可重写的成员不能用于设置/验证表达式

v7pvogib  于 2023-02-17  发布在  .NET
关注(0)|答案(6)|浏览(176)

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

我在设置属性值时得到了下面的错误。我做错了什么吗?看起来我不能模拟具体的类?只能模拟接口?请帮助。

谢谢你,阿卜杜勒

g0czyy6m

g0czyy6m1#

Moq创建被模拟类型的一个实现。如果类型是一个接口,它创建一个实现接口的类。如果类型是一个类,它创建一个继承类,并且继承类的成员调用基类。但是为了这样做,它必须重写成员。如果一个类有不能被重写的成员(它们不是虚拟的、抽象的),那么Moq就不能覆盖它们来添加自己的行为。
在这种情况下,没有必要模仿PagingOptions,因为使用真实的的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只是一个保存一些数据的类,嘲笑它真的没有什么意义,它和实际的一样容易使用。

kulphzqa

kulphzqa2#

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

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

w8biq8rn3#

如果您是基于原始标题Non-overridable members may not be used in setup / verification expressions得出这个问题的,并且其他答案都没有帮助,那么您可能希望看看反射是否能够满足您的测试需求。
假设您有一个类Foo,其属性定义为public int I { get; private set; }
如果你尝试了答案中的各种方法,其中很少有适合这种情况的,但是你可以使用.net反射来设置一个示例变量的值,并且仍然在代码中保持相当好的重构支持。
下面是使用privatesetter设置属性的代码片段:

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

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

pinkon5k

pinkon5k4#

我想改进Scott's答案并给予一般答案
如果类型是一个类,它创建一个继承类,继承类的成员调用基类。但是为了做到这一点,它必须重写成员。如果一个类有不能被重写的成员(它们不是虚拟的,抽象的),那么Moq不能重写它们来添加自己的行为。
在我的情况下,我必须使 prop 虚拟化。所以对你的类代码的回答是:

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

jbose2ul5#

有时候,您可能使用第三方库中的类,该类具有可以直接设置或模拟的属性。
如果上面的答案不充分,您还可以直接调用setter方法,例如,当一个类具有名为“Id”的属性而没有可访问的setter时:

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

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

yc0p9oo06#

在我的例子中,我是在模拟一个非虚的公共方法,让这个方法虚化就可以了。
作为一个老Java开发者,我习惯了所有的公共方法都已经是虚的,没有必要单独标记为虚的,这样子类就可以覆盖它们,C#在这里就不一样了。
也许有人可以解释一下,在C#中,为了测试的目的,是否可以将生产代码中的公共方法标记为虚拟的。

相关问题