【问题标题】: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 属性设为虚拟 - 测试通过。

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

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

【问题讨论】:

    标签: c# asp.net-mvc unit-testing controller moq


    【解决方案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/ 以获得全面的解决方案,它允许您模拟密封类、非虚拟方法等。非常强大。

    【讨论】:

      猜你喜欢
      • 2015-09-20
      • 2021-04-28
      • 1970-01-01
      • 2013-10-21
      • 2011-03-01
      • 1970-01-01
      • 2015-11-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多