2011-04-14 56 views
31

我正在做一些單元測試,並使用Moq嘲諷一些屬性。爲什麼我想模擬的屬性需要虛擬?

現在,這是一個控制器測試(ASP.NET MVC 3)。我的控制器來自摘要控制器,稱爲摘要控制器

該控制器對Http上下文具有依賴性(爲了執行諸如主題,基於HTTP HOST頭的域特定邏輯等)。 - 在構造函數設置它

public abstract class AbstractController : Controller 
{ 
    public WebSiteSettings WebSiteSettings { get; private set; } 

    // other code 
} 

通知私人組:

這是通過一個名爲WebSiteSettings屬性來實現。所以,我改變了它使用的接口,而這正是我一直嘲笑:

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財產虛擬 - 測試通過。

但我不明白爲什麼我需要這樣做。我不是覆蓋財產,我只是嘲笑它是如何設置的。

我錯過了什麼,或者做錯了什麼?

回答

48

Moq和其他類似的模擬框架只能模擬具體類上的接口,抽象方法/屬性(在抽象類上)或虛擬方法/屬性。

這是因爲它會生成一個代理來實現接口或創建派生類來覆蓋這些可覆蓋的方法以攔截調用。

+0

所以....我做了什麼是唯一的方法? – RPM1984 2011-04-14 23:47:11

+2

只有Moq的方式。其他如TypeMock Isolator,Moles等可攔截這些方法/屬性 – aqwert 2011-04-15 00:08:57

+2

哦,哇!這個魔術字是界面! – 321X 2012-11-11 23:17:08

3

「所以......我做了什麼是唯一的方法?」

沒有不是唯一的方法 - 你實現一個接口和嘲笑它要好得多。那麼你的實際方法可以是虛擬的或不是你選擇的。

+0

我對這個問題很感興趣,但不明白! Giles,你能否詳細說明一下,或許使用RPM1984的最初例子? – MrBlueSky 2012-09-27 07:19:49

0

儘管之前所說的都是真實的,但值得一提的是,代理模擬方法(如moq使用的方法)並不是唯一可能的方法。

檢查http://www.typemock.com/爲一個全面的解決方案,它允許你嘲笑密封類,非虛擬方法等非常強大。

4

我創建了一個接口和包裝類。例如

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

顯然,你可能需要包括更多的屬性/方法。但是有竅門。

其他嘲笑問題,如修改當前日期時間(I總是使用UTC時間日期)另一個有用的技巧:

public interface IDateTimeUtcNowProvider 
{ 
    DateTime UtcNow { get; } 
} 

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

例如如果你有一個每x分鐘運行一次的服務,你可以模擬出IDateTimeProvider並返回一個稍後檢查服務再次運行的時間......或者其他任何東西。