2009-04-07 18 views
7

Próbuję przeprowadzić test jednostkowy kodu za pomocą NUnit. Mam metodę: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); 
    } 

Moja domyślna instalacja kontrolna to fabryka kontrolera StructureMap z MvcContrib. Używam również MvcMockHelpers od MvcContrib, aby pomóc mi kpić z HttpContextBase.

Kontroler jestem próby testu wywołuje powyższą metodę RenderRoute i wysadza w:

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

z błędem:

Controllers.WidgetControllerTests.CanCreateWidgetOnPage: System.Web.HttpException: Inicjator typu dla "System.Web.Compilation.CompilationLock" rzucił wyjątek. ----> System.TypeInitializationException: Inicjator typu dla "System.Web.Compilation.CompilationLock" rzucił wyjątek. ----> System.NullReferenceException: Odwołanie do obiektu nie jest ustawione na wystąpienie obiektu.

Jestem całkiem nowy w testowaniu jednostkowym/szyderczym i istnieje możliwość, że nie widzę czegoś prostego.

Oto test Jestem obecnie pracuje:

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

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

Każda pomoc będzie mile widziane.

I uproszczone problemu w dół do tego prostego testu jednostkowego:

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

Odpowiedz

3

wpadłem na tej samej kwestii podczas próby badanej jednostki fabryki kontrolera napisałem.

Wydaje się, że problem pochodzi z ControllerTypeCache, który próbuje wykonać iterację wszystkich skojarzonych złożeń podczas pierwszego wywołania i używa BuildManager w tym celu. DefaultControllerFactory wydaje się być dość rozszerzalny w tym poprzez użycie właściwości BuildManager do interakcji z instancją, zamiast bezpośrednio sprzężonych, ale niestety własność jest oznaczona jako wewnętrzna. Testy jednostek szkieletowych MVC są w stanie uzyskać dostęp do wewnętrznych elementów zespołu MVC, w przeciwieństwie do reszty z nas.

Po sprawdzeniu, w jaki sposób jednostka MVCContrib testuje swoje fabryki kontrolerów, znalazłem, że używają pomocnika metody rozszerzenia, który przesłania pamięć podręczną kontrolera za pomocą odbicia, aby uzyskać dostęp do własności prywatnej.

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

Po dodając, że do mojego projektu badawczego jednostka udało mi się dodać własny typ MockController do pamięci podręcznej typu kontroler używając controllerFactory.InitializeWithControllerTypes(new[] {typeof(MockController)});