为什么我想要模拟的属性需要是虚拟的?

时间:2011-04-14 23:35:19

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

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

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

此控制器依赖于Http Context(为了执行主题,基于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 Context以便它读取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属性虚拟 - 测试通过。

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

我错过了什么,或者做错了吗?

4 个答案:

答案 0 :(得分:58)

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

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

答案 1 :(得分:4)

我已经创建了一个接口和包装类。 e.g。

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

显然,您可能需要包含更多属性/方法。但是诀窍。

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

public interface IDateTimeUtcNowProvider
{
    DateTime UtcNow { get; } 
}

public class DateTimeUtcNowProvider : IDateTimeUtcNowProvider
{
    public DateTime UtcNow { get { return DateTime.UtcNow; } }
}

e.g。如果你有一个每x分钟运行一次的服务,你可以模拟出IDateTimeProvider并返回一个稍后检查服务再次运行的时间......或者其他什么。

答案 2 :(得分:3)

“所以......我所做的只是唯一的方式?”

不是唯一的方法 - 你最好实现一个界面并嘲笑它。那么您的实际方法可以是虚拟的,也可以不是您选择的。

答案 3 :(得分:0)

虽然之前说的都是真的,但值得知道代理模拟方法(就像moq使用的方法)我不是唯一可能的方法。

检查http://www.typemock.com/是否有全面的解决方案,允许您模拟密封类,非虚拟方法等。非常强大。