2012-01-03 37 views
3

我正在編寫測試C#MVC3應用程序的代碼。我可以測試控制器,但是如何測試視圖中的代碼?這包括JavaScript和剃刀風格的代碼。如何在mvc3中使用razor語法測試視圖?

是否有任何工具可用於模擬C#中的視圖或測試視圖和JavaScript?

回答

2

以下是關於測試視圖的渲染輸出。例如,可以將此文本輸出加載到DOM中,以便使用XPath進行進一步分析(對於XHTML使用XmlReader或對SGML樣式的HTML使用HtmlAgilityPack)。通過一些好的幫助方法,可以輕鬆檢查視圖的特定部分,例如測試//a[@href='#']或其他任何您想測試的部分。這有助於使單元測試更加穩定。

當使用Razor而不是「吹起來的」WebForms引擎時,人們會認爲這很容易,但事實證明,相反,由於許多Razor視圖引擎和視圖使用部件的內部工作原理HtmlHelper尤其是)HTTP請求生命週期。事實上,正確地測試生成的輸出需要很多運行代碼才能獲得可靠和合適的結果,如果使用諸如便攜式區域(來自MVCContrib項目)等特殊內容等等,那麼更加如此。

操作和URL的HTML幫助程序要求路由已正確初始化,路由字典已正確設置,控制器也必須存在,並且還有其他與加載視圖數據有關的「陷阱」,例如設置視圖數據字典...

我們最終創建了一個ViewRenderer類,它將實際在您的Web的物理路徑上實例化一個應用程序主機以進行測試(可以靜態緩存,每個單一測試重新初始化由於初始化滯後而不切實際):

host = (ApplicationHost)System.Web.Hosting.ApplicationHost.CreateApplicationHost(typeof(ApplicationHost), "/", physicalDir.FullName); 

ApplicationHost類繼承自MarshalByRefObject,因爲主機將加載到單獨的應用程序域中。主機執行各種不聖潔的初始化東西,以便正確初始化HttpApplication(註冊路由等的global.asax.cs中的代碼),同時禁用某些方面(如身份驗證和授權)。 被警告,嚴重的黑客入侵。使用風險自負。

public ApplicationHost() { 
    ApplicationMode.UnitTesting = true; // set a flag on a global helper class to indicate what mode we're running in; this flag can be evaluated in the global.asax.cs code to skip code which shall not run when unit testing 
    // first we need to tweak the configuration to successfully perform requests and initialization later 
    AuthenticationSection authenticationSection = (AuthenticationSection)WebConfigurationManager.GetSection("system.web/authentication"); 
    ClearReadOnly(authenticationSection); 
    authenticationSection.Mode = AuthenticationMode.None; 
    AuthorizationSection authorizationSection = (AuthorizationSection)WebConfigurationManager.GetSection("system.web/authorization"); 
    ClearReadOnly(authorizationSection); 
    AuthorizationRuleCollection authorizationRules = authorizationSection.Rules; 
    ClearReadOnly(authorizationRules); 
    authorizationRules.Clear(); 
    AuthorizationRule rule = new AuthorizationRule(AuthorizationRuleAction.Allow); 
    rule.Users.Add("*"); 
    authorizationRules.Add(rule); 
    // now we execute a bogus request to fully initialize the application 
    ApplicationCatcher catcher = new ApplicationCatcher(); 
    HttpRuntime.ProcessRequest(new SimpleWorkerRequest("/404.axd", "", catcher)); 
    if (catcher.ApplicationInstance == null) { 
     throw new InvalidOperationException("Initialization failed, could not get application type"); 
    } 
    applicationType = catcher.ApplicationInstance.GetType().BaseType; 
} 

ClearReadOnly方法使用反射來使內存web配置可變:

private static void ClearReadOnly(ConfigurationElement element) { 
    for (Type type = element.GetType(); type != null; type = type.BaseType) { 
     foreach (FieldInfo field in type.GetFields(BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.DeclaredOnly).Where(f => typeof(bool).IsAssignableFrom(f.FieldType) && f.Name.EndsWith("ReadOnly", StringComparison.OrdinalIgnoreCase))) { 
      field.SetValue(element, false); 
     } 
    } 
} 

ApplicationCatcher是一個「空」 TextWriter,其存儲應用程序實例。我無法找到另一種方式來初始化應用程序實例並獲取它。它的核心非常簡單。

public override void Close() { 
    Flush(); 
} 

public override void Flush() { 
    if ((applicationInstance == null) && (HttpContext.Current != null)) { 
     applicationInstance = HttpContext.Current.ApplicationInstance; 
    } 
} 

現在,這使我們能夠呈現出幾乎任何(剃刀)查看,就好像它是在一個真正的web服務器託管,爲使其產生幾乎完整的HTTP生命週期:

private static readonly Regex rxControllerParser = new Regex(@"^(?<areans>.+?)\.Controllers\.(?<controller>[^\.]+)Controller$", RegexOptions.CultureInvariant|RegexOptions.IgnorePatternWhitespace|RegexOptions.ExplicitCapture); 

public string RenderViewToString<TController, TModel>(string viewName, bool partial, Dictionary<string, object> viewData, TModel model) where TController: ControllerBase { 
    if (viewName == null) { 
     throw new ArgumentNullException("viewName"); 
    } 
    using (StringWriter sw = new StringWriter()) { 
     SimpleWorkerRequest workerRequest = new SimpleWorkerRequest("/", "", sw); 
     HttpContextBase httpContext = new HttpContextWrapper(HttpContext.Current = new HttpContext(workerRequest)); 
     RouteData routeData = new RouteData(); 
     Match match = rxControllerParser.Match(typeof(TController).FullName); 
     if (!match.Success) { 
      throw new InvalidOperationException(string.Format("The controller {0} doesn't follow the common name pattern", typeof(TController).FullName)); 
     } 
     string areaName; 
     if (TryResolveAreaNameByNamespace<TController>(match.Groups["areans"].Value, out areaName)) { 
      routeData.DataTokens.Add("area", areaName); 
     } 
     routeData.Values.Add("controller", match.Groups["controller"].Value); 
     ControllerContext controllerContext = new ControllerContext(httpContext, routeData, (ControllerBase)FormatterServices.GetUninitializedObject(typeof(TController))); 
     ViewEngineResult engineResult = partial ? ViewEngines.Engines.FindPartialView(controllerContext, viewName) : ViewEngines.Engines.FindView(controllerContext, viewName, null); 
     if (engineResult.View == null) { 
      throw new FileNotFoundException(string.Format("The view '{0}' was not found", viewName)); 
     } 
     ViewDataDictionary<TModel> viewDataDictionary = new ViewDataDictionary<TModel>(model); 
     if (viewData != null) { 
      foreach (KeyValuePair<string, object> pair in viewData) { 
       viewDataDictionary.Add(pair.Key, pair.Value); 
      } 
     } 
     ViewContext viewContext = new ViewContext(controllerContext, engineResult.View, viewDataDictionary, new TempDataDictionary(), sw); 
     engineResult.View.Render(viewContext, sw); 
     return sw.ToString(); 
    } 
} 

也許這幫助你獲得一些結果。總的來說,很多人認爲測試視圖的麻煩並不值得。我會讓你成爲那個評委。

相關問題