为啥我要模拟的属性需要是虚拟的?

Posted

技术标签:

【中文标题】为啥我要模拟的属性需要是虚拟的?【英文标题】:Why does the property I want to mock need to be virtual?为什么我要模拟的属性需要是虚拟的? 【发布时间】:2011-04-14 23:35:18 【问题描述】:

我正在做一些单元测试,并使用 Moq 模拟一些属性。

现在,这是一个 控制器 测试 (ASP.NET MVC 3)。我的控制器派生自一个 abstract 控制器,称为 AbstractController

此控制器依赖于 Http 上下文(为了执行主题化、基于 HTTP HOST 标头的特定于域的逻辑等事情)。

这是通过名为 WebSiteSettings 的属性完成的:

public abstract class AbstractController : Controller

   public WebSiteSettings WebSiteSettings  get; private set; 

   // other code

注意私有集 - ctor 设置它。因此,我将其更改为使用接口,这就是我所嘲笑的:

public IWebSiteSettings WebSiteSettings  get; private set; 

然后,我创建了一个“FakeWebSiteSettings”,它模拟 Http 上下文,以便它读取 HTTP 标头。

问题是,当我运行测试时,我得到一个 NotSupportedException:

非虚拟(在 VB 中可覆盖)成员上的设置无效:x => x.WebSiteSettings

以下是相关的模拟代码:

var mockWebSiteSettings = new Mock<FakeWebSiteSettings>();
var mockController = new Mock<MyController>(SomeRepository);
mockController.Setup(x => x.WebSiteSettings).Returns(mockWebSiteSettings.Object);

_controller = mockController.Object;

var httpContextBase = MvcMockHelpers.FakeHttpContext();
httpContextBase.Setup(x => x.Request.ServerVariables).Returns(new NameValueCollection
    
        "HTTP_HOST","localhost.www.mydomain.com", 
);
_controller.SetFakeControllerContext(httpContextBase.Object);

如果我将WebsiteSettings 属性设为虚拟 - 测试通过。

但我不明白为什么我需要这样做。我实际上并没有覆盖该属性,我只是在嘲笑它是如何设置的。

是我遗漏了什么,还是做错了?

【问题讨论】:

【参考方案1】:

Moq 和其他类似的模拟框架只能模拟接口、抽象方法/属性(在抽象类上)或虚拟方法/属性在具体类上。

这是因为它会生成一个代理来实现接口或创建一个派生类来覆盖那些可覆盖的方法以拦截调用。

【讨论】:

Moq 的唯一途径。其他的比如TypeMock Isolator,Moles可以拦截这些方法/属性【参考方案2】:

我创建了一个接口和包装类。例如

    public interface IWebClient
    
        string DownloadString(string url);
    

    public class WebClient : IWebClient
    
        private readonly System.Net.WebClient _webClient = new System.Net.WebClient();

        public string DownloadString(string url)
        
            return _webClient.DownloadString(url);
        
    

然后在你的单元测试中模拟出接口:

        var mockWebClient = new Mock<IWebClient>();

显然,您可能需要包含更多属性/方法。但确实有效果。

另一个对其他 mocking 问题有用的技巧,例如修改当前日期时间(我总是使用 UTC 日期时间):

public interface IDateTimeUtcNowProvider

    DateTime UtcNow  get;  


public class DateTimeUtcNowProvider : IDateTimeUtcNowProvider

    public DateTime UtcNow  get  return DateTime.UtcNow;  

例如如果您有一个每 x 分钟运行一次的服务,您可以模拟 IDateTimeProvider 并返回一个稍后的时间以检查该服务是否再次运行......或其他什么。

【讨论】:

对于 .NET 5 ISystemClock【参考方案3】:

“所以....我所做的是唯一的方法吗?”

不是唯一的方法 - 你最好实现一个接口并模拟它。然后,您的实际方法可以是虚拟的,也可以不是虚拟的。

【讨论】:

我对这个答案很感兴趣,但不明白! Giles,您能否详细说明一下,也许使用 RPM1984 的原始示例?【参考方案4】:

尽管前面所说的都是正确的,但值得知道的是,代理模拟方法(如 moq 使用的方法)并不是唯一可能的方法。

查看http://www.typemock.com/ 以获得全面的解决方案,它允许您模拟密封类、非虚拟方法等。非常强大。

【讨论】:

以上是关于为啥我要模拟的属性需要是虚拟的?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在集合视图单元格中以编程方式创建的按钮需要是惰性变量?

为啥在多重继承的情况下QObject需要是第一个

为啥包含 main 方法的类在 Java 中不需要是公共的? [复制]

为啥在字符串中使用 javascript 转义字符的引号需要是 \\' 而不是 \'

Keras 嵌入层遮罩。为啥 input_dim 需要是 |vocabulary| + 2?

ios为啥cordova命令安装出错