ASP.NET Mvc - System.Web.Compilation.CompilationLock - PullRequest
7 голосов
/ 07 апреля 2009

Я пытаюсь протестировать код с помощью 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);
    }

Моя фабрика контроллеров по умолчанию - фабрика контроллеров StructureMap от MvcContrib. Я также использую MvcMockHelpers от MvcContrib, чтобы помочь мне издеваться над HttpContextBase.

Контроллер, который я пытаюсь проверить, вызывает вышеупомянутый метод RenderRoute и взрывается при:

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

С ошибкой:

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

Любая помощь будет оценена.

Я упростил задачу до этого простого модульного теста:

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

1 Ответ

3 голосов
/ 12 июня 2009

Я столкнулся с той же проблемой при попытке модульного тестирования фабрики контроллеров, которую я написал.

Проблема возникает из-за того, что ControllerTypeCache пытается перебрать все связанные сборки при первом вызове и использует для этого BuildManager. В этом случае DefaultControllerFactory выглядит довольно расширяемым, используя свойство BuildManager для взаимодействия с экземпляром, а не напрямую соединяясь, но, к сожалению, свойство помечается как внутреннее. Модульные тесты фреймворка 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);
    }
}

После добавления этого в мой проект модульных тестов я смог добавить свой собственный тип MockController в кеш типа контроллера, используя controllerFactory.InitializeWithControllerTypes(new[] {typeof(MockController)});

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...