2009-04-07 4 views
7

Je tente de tester un peu de code en utilisant NUnit. J'ai une méthode: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); 
    } 

Mon défaut ControllerFactory est une usine de contrôleur de StructureMap de MvcContrib. J'utilise aussi les MvcMockHelpers de MvcContrib pour m'aider à simuler le HttpContextBase.

Le contrôleur je tente de tester appelle la méthode RenderRoute ci-dessus et explose à:

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

Avec l'erreur:

Controllers.WidgetControllerTests.CanCreateWidgetOnPage: System.Web.HttpException: Le type initializer pour 'System.Web.Compilation.CompilationLock' a lancé une exception. ----> System.TypeInitializationException: l'initialiseur de type pour 'System.Web.Compilation.CompilationLock' a généré une exception. ----> System.NullReferenceException: Référence d'objet non définie sur une instance d'un objet. Je suis relativement nouveau dans les tests unitaires/moqueurs et c'est une possibilité que je ne vois pas quelque chose de simple.

Voici le test que je suis en cours d'exécution:

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

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

Toute aide serait appréciée.

J'ai simplifié le problème à ce simple test unitaire:

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

Répondre

3

je suis tombé sur cette même question lors d'une tentative de test unitaire d'une usine de contrôleur je l'ai écrit.

Le problème semble provenir du ControllerTypeCache qui tente de parcourir tous les assemblys associés lors du premier appel et utilise BuildManager pour ce faire. Le DefaultControllerFactory semble être assez extensible en utilisant une propriété BuildManager pour interagir avec une instance au lieu d'être couplé directement mais malheureusement la propriété est marquée comme interne. Les tests de l'unité de base MVC sont capables d'accéder aux composants internes de l'assemblage MVC, contrairement à nous tous. Après avoir examiné comment l'unité MVCContrib teste leurs fabriques de contrôleurs, j'ai constaté qu'ils utilisaient une méthode d'extension auxiliaire qui remplace le cache du contrôleur en utilisant la réflexion pour accéder à une propriété privée.

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

Après avoir ajouté que mon projet de test unitaire, j'ai pu ajouter mon propre type de MockController dans le cache de type de contrôleur en utilisant controllerFactory.InitializeWithControllerTypes(new[] {typeof(MockController)});