2012-01-03 7 views
3

私はC#MVC3アプリケーションをテストするコードを書いています。私はコントローラをテストすることができますが、どのようにビューのコードをテストするのですか?これには、javascriptとひげ剃りされたコードが含まれます。mvc3のカミソリ構文を使用してビューをテストするにはどうすればよいですか?

C#でビューやテストビュー、およびJavaScriptをモックアウトできるツールはありますか?

答えて

1

を使用してASP.NET MVC Viewエンジンをモックする方法については、vanthesharkの記事を参照してください。

2

以下は、ビューのレンダリングされた出力のテストについてです。このテキスト出力は、例えば、XHTMLの場合はXmlReader、SGMLスタイルのHTMLの場合はHtmlAgilityPackを使用して、さらに解析するためにDOMにロードできます。いくつかの素晴らしいヘルパーメソッドを使用すると、ビューの特定の部分をチェックするための簡単なチェックが可能です(例えば、テストする//a[@href='#']など)。これはユニットテストをより安定させるのに役立ちます。

「吹き上がった」WebFormsエンジンの代わりにRazorを使用すると、これは簡単だと予想されますが、Razorビューエンジンの多くの内部機能と部品を使用したビューのために、 HtmlHelper)を使用します。実際には、生成された出力を適切にテストするには、信頼性の高い適切な結果を得るために多くの実行コードが必要です。MVCContribプロジェクトなどのポータブル領域などのエキゾチックなものをミックスに使用する場合はさらにそうです。

アクションとURLのHTMLヘルパーは、ルーティングが適切に初期化されていること、ルート辞書が適切に設定されていること、コントローラが存在していなければならないこと、ビューのデータ読み込みに関連するその他の「問題点」ビューデータディクショナリを設定する...

実際にテストするウェブの物理パス上にアプリケーションホストをインスタンス化するViewRendererクラスを作成しました(静的にキャッシュすることができます。初期化の遅れのために実用的ではない):

ApplicationHostクラスは、ホストが別のアプリケーションドメインにロードされるため、MarshalByRefObjectから継承します。ホストは、いくつかの側面(認証や許可など)を無効にしながら、HttpApplication(経路を登録するglobal.asax.csのコード)を適切に初期化するために、あらゆる種類の不正な初期設定を行います。 重大なハッキングを事前に警告してください。自己責任。 ClearReadOnly方法は、可変メモリ内のWeb設定を行うためにリフレクションを使用

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; 
} 

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あります。アプリケーションインスタンスを初期化して取得する別の方法が見つかりませんでした。そのコアはかなりシンプルです。多分これ

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

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

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

これは今それをレンダリングするために、ほぼ完全なHTTPのライフサイクルを作成し、それが実際のWebサーバーでホストされているかのように、ほぼすべての(レイザー)ビューをレンダリングすることを可能にいくつかの結果を得るのに役立ちます。一般的に、多くの人々は、ビューの見直しの面倒が努力に値するものではないと言います。私はあなたをその裁判官にさせるでしょう。

関連する問題