2011-10-31 155 views
2

所以這真的適用於像HttpContext,ConfigurationManager等幾個不同的類。有幾種不同的方法來處理這個問題,我一直使用包裝類來處理這個東西,但我想看看最常見的社區做法是...MVC3 HttpContext單元測試/模擬選項

  1. 包裝類 - 例如我會有一個HttpContextService,我通過構造函數傳入,通過平面方法調用公開所有相同的功能。
  2. 包裝類(第2部分) - 例如我會擁有SPECIFIC服務類,比如MembershipService,在幕後使用HttpContext。函數與1相同,但命名方案/使用模式有點不同,因爲您通過特定的服務而不是一個單片封裝來暴露特定的函數。缺點是需要注入的服務類數量增加了,但是當你不需要單片封裝的所有功能時,你會得到一些模塊化。
  3. ActionFilters和參數 - 使用ActionFilter自動傳遞每個函數所需的某些值。僅限MVC,並將您限制爲控制器方法,而1和2可以在整個項目中一般使用,甚至可以與此選項結合使用。
  4. 直接模擬HttpContextBase並設置ControllerContext - 有幾個模擬框架擴展方法可以幫助解決這個問題,但實質上需要您根據需要直接設置。不需要抽象,這很好,並且可以在非控制器測試中重複使用。儘管如此,仍然留下了ConfigurationManager和其他靜態方法調用的問題,所以您最終可能會注入該ANYWAY,但會以其他方式訪問HttpContext。

現在我有點在做數字1,所以我有一個HttpContextService和一個ConfigurationManagerService等等,然後我會注入,但我將來會傾向於2。 3對我的口味來說似乎有點太混亂了,但是我可以看到控制器方法的吸引力,並且對於其他使用這些靜態類的代碼區域需要一個完全獨立的解決方案,這對我來說是一種窮人。 .. 4對我來說仍然很有趣,因爲它在基本功能方面似乎是最「自然的」,並且利用了MVC的內置方法。

那麼現在最流行的最佳實踐是什麼?人們在野外看到和使用什麼?

回答

0

HttpContext,HttpRequest,HttpResponse等已經有了「包裝器」類.MVC框架使用這些類,你可以通過控制器上下文將它們提供給控制器。您不需要模擬控制器上下文,因爲您可以簡單地使用適當的值創建控制器上下文。我發現唯一難以嘲笑的是幫手,UrlHelper和HtmlHelper。那些有一些相對較深的依賴性。你可以用一種合理的方式僞造它們,UrlHelper如下所示。

var httpContext = MockRepository.GenerateMock<HttpContextBase>(); 
var routeData = new RoutedData(); 

var controller = new HomeController(); 
controller.ControllerContext = new ControllerContext(httpContext, routeData, controller); 
controller.Url = UrlHelperFactory.CreateUrlHelper(httpContext, routeDate); 

其中

public static class UrlHelperFactory 
{ 
    public static UrlHelper CreateUrlHelper(HttpContextBase httpContext, RouteData routeData) 
    { 
     return CreateUrlHelper(httpContext, routeData, "/"); 
    } 

    public static UrlHelper CreateUrlHelper(HttpContextBase httpContext, RouteData routeData, string url) 
    { 
     string urlString = string.Format("http://localhost/{0}/{1}/{2}", routeData.Values["controller"], routeData.Values["action"], routeData.Values["id"]).TrimEnd('/'); 

     var uri = new Uri(urlString); 

     if (httpContext.Request == null) 
     { 
      httpContext.Stub(c => c.Request).Return(MockRepository.GenerateStub<HttpRequestBase>()).Repeat.Any(); 
     } 

     httpContext.Request.Stub(r => r.Url).Return(uri).Repeat.Any(); 
     httpContext.Request.Stub(r => r.ApplicationPath).Return("/").Repeat.Any(); 

     if (httpContext.Response == null) 
     { 
      httpContext.Stub(c => c.Response).Return(MockRepository.GenerateStub<HttpResponseBase>()).Repeat.Any(); 
     } 
     if (url != "/") 
     { 
      url = url.TrimEnd('/'); 
     } 

     httpContext.Response.Stub(r => r.ApplyAppPathModifier(Arg<string>.Is.Anything)).Return(url).Repeat.Any(); 

     return new UrlHelper(CreateRequestContext(httpContext, routeData), GetRoutes()); 
    } 

    public static RequestContext CreateRequestContext(HttpContextBase httpContext, RouteData routeData) 
    { 
     return new RequestContext(httpContext, routeData); 
    } 

    // repeat your route definitions here!!! 
    public static RouteCollection GetRoutes() 
    { 
     RouteCollection routes = new RouteCollection(); 
     routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 


     routes.MapRoute(
      "Default",            // Route name 
      "{controller}/{action}/{id}",       // URL with parameters 
      new { controller = "home", action = "index", id = "" } // Parameter defaults 
     ); 

     return routes; 
    } 
} 
+0

權,這將是選項4,和HttpContextBase這僅適用。正如你所展示的那樣,我可以抽象出這個測試設置代碼,但是這不包括像ConfigurationManager等其他靜態類,所以你最終得到了解決同一問題的兩種解決方案,而選項1或2將它們統一起來。正如我所說,他們是兩種不同的解決方案,我假設你更喜歡在這裏使用選項4 ... –

+0

除了你沒有提供一個模擬控制器上下文,而是一個實際的控制器上下文與嵌入的相關模擬。我使用構造函數注入來處理FormsAuthentication,MembershipProvider等事情,並使用適當的包裝器,儘管我只在我的AccountController上使用這些構造器注入。對於其他東西,我嘲笑通過HttpContext,如請求,響應,用戶,會話等。 – tvanfosson

+0

Doh!我原來的帖子裏寫錯了,錯誤。這就是我想說的。現在修復。對於爲什麼你喜歡這種方法而不是將對象作爲一個整體進行封裝(選項1)還是部分按角色(選項2),或者我用「正在注入的很多依賴項正在快速增長」的方式來實現它的評論? –