2009-04-07 4 views
7

Ich versuche Unit-Test Code mit NUnit zu testen. Ich habe eine Methode:ASP.NET Mvc - System.Web.Compilation.CompilationLock

public static string RenderRoute(HttpContextBase context, RouteValueDictionary values) 
    { 
     var routeData = new RouteData(); 
     foreach (var kvp in values) 
     { 
      routeData.Values.Add(kvp.Key, kvp.Value); 
     } 

     string controllerName = routeData.GetRequiredString("controller"); 
     var requestContext = new RequestContext(context, routeData); 
     IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory(); 
     IController controller = factory.CreateController(requestContext, controllerName); 

     var ActionInvoker = new ControllerActionInvoker(); 
     var controllerContext = new ControllerContext(requestContext, (ControllerBase)controller); 
     ((ControllerBase)controller).ControllerContext = controllerContext; 

     string actionName = routeData.GetRequiredString("action"); 

     Action action = delegate { ActionInvoker.InvokeAction(controllerContext, actionName); }; 

     return new BlockRenderer(context).Capture(action); 
    } 

Meine Standardcontroller ist eine StructureMap Controller Factory von MvcContrib. Ich benutze auch die MvcMockHelpers von MvcContrib, um mir zu helfen, die HttpContextBase zu verspotten.

Der Controller ich zu Test bin versucht, die oben RenderRoute Methode aufruft und sprengt bei:

IController controller = factory.CreateController(requestContext, controllerName); 

Mit dem Fehler:

Controllers.WidgetControllerTests.CanCreateWidgetOnPage: System.Web.HttpException: Die Typinitialisierer für 'System.Web.Compilation.CompilationLock' hat eine Ausnahme ausgelöst. ----> System.TypeInitializationException: Der Typinitialisierer für 'System.Web.Compilation.CompilationLock' hat eine Ausnahme ausgelöst. ----> System.NullReferenceException: Objektreferenz wurde nicht auf eine Instanz eines Objekts gesetzt.

Ich bin ziemlich neu zu Unit Testing/Mocking und es ist eine Möglichkeit, ich sehe nicht etwas einfaches. Hier

ist der Test ich zur Zeit läuft:

[Test] 
    public void Test() 
    { 
     HttpContextBase context = MvcMockHelpers.DynamicHttpContextBase(); 
     string s = RenderExtensions.RenderAction<HomeController>(context, a => a.About()); 

     Console.WriteLine(s); 
     Assert.IsNotNullOrEmpty(s); 
    } 

Jede Hilfe würde geschätzt.

ich vereinfacht das Problem auf diesem einfachen Gerät zu testen:

[Test] 
    public void Test2() 
    { 
     HttpContextBase context = MvcMockHelpers.DynamicHttpContextBase(); 
     var routeData = new RouteData(); 
     routeData.Values.Add("Controller", "Home"); 
     routeData.Values.Add("Action", "About"); 


     string controllerName = routeData.GetRequiredString("controller"); 
     var requestContext = new RequestContext(context, routeData); 
     IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory(); 
     IController controller = factory.CreateController(requestContext, controllerName); 

     Assert.IsNotNull(controller); 
    } 

Antwort

3

Ich lief in das gleiche Problem, wenn Komponententest einen Controller Fabrik versucht, ich schrieb.

Das Problem scheint von dem ControllerTypeCache zu kommen, der beim ersten Aufruf versucht, alle zugeordneten Assemblys zu durchlaufen, und dabei BuildManager verwendet. Die DefaultControllerFactory scheint in diesem Fall ziemlich erweiterbar zu sein, indem eine BuildManager-Eigenschaft verwendet wird, um mit einer Instanz zu interagieren, anstatt direkt gekoppelt zu werden, aber unglücklicherweise ist die Eigenschaft intern markiert. Die MVC-Framework-Unit-Tests können im Gegensatz zu den anderen auf die Interna der MVC-Baugruppe zugreifen.

Nachdem ich gesehen habe, wie die MVCContrib-Einheit ihre Controllerfabriken testet, habe ich festgestellt, dass sie einen Erweiterungsmethoden-Helper verwenden, der den Controller-Cache überschreibt, um auf eine private Eigenschaft zuzugreifen.

using System; 
using System.Linq; 
using System.Reflection; 
using System.Web.Mvc; 

public static class ControllerFactoryTestExtension 
{ 
    private static readonly PropertyInfo _typeCacheProperty; 
    private static readonly FieldInfo _cacheField; 

    static ControllerFactoryTestExtension() 
    { 
     _typeCacheProperty = typeof(DefaultControllerFactory).GetProperty("ControllerTypeCache", BindingFlags.Instance | BindingFlags.NonPublic); 
     _cacheField = _typeCacheProperty.PropertyType.GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance); 
    } 

    /// <summary> 
    /// Replaces the cache field of a the DefaultControllerFactory's ControllerTypeCache. 
    /// This ensures that only the specified controller types will be searched when instantiating a controller. 
    /// As the ControllerTypeCache is internal, this uses some reflection hackery. 
    /// </summary> 
    public static void InitializeWithControllerTypes(this IControllerFactory factory, params Type[] controllerTypes) 
    { 
     var cache = controllerTypes 
      .GroupBy(t => t.Name.Substring(0, t.Name.Length - "Controller".Length), StringComparer.OrdinalIgnoreCase) 
      .ToDictionary(g => g.Key, g => g.ToLookup(t => t.Namespace ?? string.Empty, StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase); 

     var buildManager = _typeCacheProperty.GetValue(factory, null); 
     _cacheField.SetValue(buildManager, cache); 
    } 
} 

Nach dem Hinzufügen, dass ich konnte mein Unit-Test-Projekt zu meinem eigenen MockController Typ Reglertyp Cache hinzufügen mit controllerFactory.InitializeWithControllerTypes(new[] {typeof(MockController)});