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

7 c# asp.net-mvc

我正在尝试使用NUnit对一些代码进行单元测试.我有一个方法:

    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);
    }
Run Code Online (Sandbox Code Playgroud)

我的默认controllerfactory是MvcContrib的StructureMap控制器工厂.我也使用MvcContrib的MvcMockHelpers来帮助我模拟HttpContextBase.

我试图测试的控制器调用上面的RenderRoute方法并在以下情况下爆炸:

IController controller = factory.CreateController(requestContext, controllerName);
Run Code Online (Sandbox Code Playgroud)

有错误:

Controllers.WidgetControllerTests.CanCreateWidgetOnPage:System.Web.HttpException:'System.Web.Compilation.CompilationLock'的类型初始值设定项引发了异常.----> System.TypeInitializationException:'System.Web.Compilation.CompilationLock'的类型初始值设定项引发异常.----> System.NullReferenceException:对象引用未设置为对象的实例.

我对单元测试/嘲笑相当新,而且有可能我看不到简单的东西.

这是我目前正在运行的测试:

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

        Console.WriteLine(s);
        Assert.IsNotNullOrEmpty(s);
    }
Run Code Online (Sandbox Code Playgroud)

任何帮助,将不胜感激.

我已将问题简化为这个简单的单元测试:

    [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);
    }
Run Code Online (Sandbox Code Playgroud)

Joh*_*ney 3

当我尝试对我编写的控制器工厂进行单元测试时,我遇到了同样的问题。

该问题似乎来自于 ControllerTypeCache 在第一次调用时尝试迭代所有关联的程序集,并使用 BuildManager 来执行此操作。通过使用 BuildManager 属性与实例交互而不是直接耦合,DefaultControllerFactory 看起来在这方面具有相当的可扩展性,但不幸的是该属性被标记为内部。与我们其他人不同,MVC 框架单元测试能够访问 MVC 程序集的内部结构。

在查看了 MVCContrib 单元如何测试其控制器工厂后,我发现他们正在使用扩展方法助手,该扩展方法助手使用反射来访问私有属性来覆盖控制器缓存。

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

将其添加到我的单元测试项目后,我可以使用以下命令将我自己的 MockController 类型添加到控制器类型缓存中controllerFactory.InitializeWithControllerTypes(new[] {typeof(MockController)});