ASP.NET Mvc - System.Web.Compilation.CompilationLock

I am trying to use unit test code using NUnit. I have a method:

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

My default controller is the StructureMap factory controller from MvcContrib. I also use MvcMockHelpers from MvcContrib to help me make fun of HttpContextBase.

The controller I'm trying to test calls the above RenderRoute method and explodes at:

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

With an error:

Controllers.WidgetControllerTests.CanCreateWidgetOnPage: System.Web.HttpException: The type initializer for "System.Web.Compilation.CompilationLock" made an exception. ----> System.TypeInitializationException: the type initializer for "System.Web.Compilation.CompilationLock" made an exception. ----> System.NullReferenceException: the reference to the object is not installed in the instance of the object.

I am new to unit testing / bullying and it is an opportunity that I don't see anything simple.

Here is the test I'm doing now:

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

Any help would be appreciated.

I simplified the problem to this simple unit test:

  [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); } 
+7
c # asp.net-mvc
source share
1 answer

I ran into the same problem while trying to unit test the factory controller that I wrote.

The problem arises from ControllerTypeCache trying to iterate through all related assemblies on the first call and using the BuildManager at the same time. The DefaultControllerFactory looks pretty extensible in this, using the BuildManager property to interact with the instance instead of directly connecting, but unfortunately the property is marked as internal. The tests of the MVC module module have access to the internal elements of the MVC assembly, unlike the rest of us.

After seeing how the MVCContrib module validates its controller factories, I find that they use a helper extension method that overrides the controller cache using reflection to access private property.

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

After adding a unit test to my project, I was able to add my own MockController type to the controller cache using controllerFactory.InitializeWithControllerTypes(new[] {typeof(MockController)});

+3
source share

All Articles