2009-04-07 9 views
7

Estoy intentando probar un código con NUnit. Tengo un método: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); 
    } 

Mi controllerfactory por defecto es una fábrica de controlador StructureMap MvcContrib. También estoy usando MvcMockHelpers de MvcContrib para ayudarme a simular el HttpContextBase.

El controlador que estoy tratando de prueba llama al método RenderRoute arriba y explota en:

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

con el error:

Controllers.WidgetControllerTests.CanCreateWidgetOnPage: System.Web.HttpException: La tipo de inicializador para 'System.Web.Compilation.CompilationLock' lanzó una excepción. ----> System.TypeInitializationException: el inicializador de tipo para 'System.Web.Compilation.CompilationLock' lanzó una excepción. ----> System.NullReferenceException: referencia de objeto no establecida en una instancia de un objeto.

Soy bastante nuevo en pruebas unitarias/burlas y es posible que no vea algo simple.

Aquí está la prueba actualmente estoy corriendo:

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

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

se agradecería cualquier ayuda.

He simplificado el problema en esta sencilla prueba de unidad:

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

Respuesta

3

me encontré con este mismo problema cuando se trata de probar la unidad controladora una fábrica que escribí.

El problema parece provenir del ControllerTypeCache que intenta iterar a través de todos los ensamblados asociados en la primera invocación y utiliza BuildManager al hacerlo. El DefaultControllerFactory parece ser bastante extensible en esto mediante el uso de una propiedad BuildManager para interactuar con una instancia en lugar de estar directamente acoplado, pero desafortunadamente la propiedad está marcada como interna. Las pruebas de unidad de marco MVC pueden acceder a las partes internas del ensamblaje MVC a diferencia del resto de nosotros.

Después de ver cómo la unidad MVCContrib prueba sus fábricas controladoras, encontré que están utilizando un asistente de método de extensión que anula la memoria caché del controlador utilizando el reflejo para acceder a una propiedad privada.

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

después de añadir que a mi proyecto de prueba unitaria pude añadir mi propio tipo MockController a la caché tipo de controlador utilizando controllerFactory.InitializeWithControllerTypes(new[] {typeof(MockController)});