2014-12-02 2 views
5

Я пытаюсь проверить, что мой базовый контроллер украшен определенным фильтром действий. Поскольку конструктор этого фильтра выглядит web.config, моя первая попытка тестирования не выполняется, потому что тестовый проект не имеет действительного файла конфигурации. Если продолжить, я использовал TestConfigProvider, который я вставляю в конструктор фильтра, но следующий тест не выполняется, потому что поставщик конфигурации не передается конструктору. Как еще можно проверить, применяется ли этот фильтр?Как проверить наличие фильтра действий с аргументами конструктора?

[TestMethod] 
public void Base_controller_must_have_MaxLengthFilter_attribute() 
{ 
    var att = typeof(BaseController).GetCustomAttribute<MaxLengthFilter>(); 
    Assert.IsNotNull(att); 
} 

ответ

22

Хорошо, вы сделали хороший первый шаг, признав, что Web.config - это просто другая зависимость и его упаковка в ConfigProvider для инъекций - отличное решение.

Но вы попадаете в одну из проблем дизайна MVC, а именно, чтобы быть дружественными к DI, атрибуты должны предоставлять только метаданные, но never actually define behavior. Это не проблема с вашим подходом к тестированию, это проблема с подходом к дизайну фильтра.

Как указано в сообщении, вы можете обойти эту проблему, разделив атрибут фильтра действий на 2 части.

  1. Атрибут, который не содержит поведения, чтобы помечать ваши контроллеры и методы действий с помощью.
  2. DI-friendly класс, который реализует IActionFilter и содержит желаемое поведение.

Подход заключается в использовании IActionFilter для проверки наличия атрибута и последующего выполнения желаемого поведения. Фильтр действий может поставляться со всеми зависимостями и затем вводиться, когда приложение составлено.

IConfigProvider provider = new WebConfigProvider(); 
IActionFilter filter = new MaxLengthActionFilter(provider); 
GlobalFilters.Filters.Add(filter); 

ПРИМЕЧАНИЕ: Если вам нужна зависимостей фильтра, чтобы иметь продолжительность жизни короче, чем одноплодной, вам нужно будет использовать GlobalFilterProvider как в this answer.

Реализация MaxLengthActionFilter будет выглядеть примерно так:

public class MaxLengthActionFilter : IActionFilter 
{ 
    public readonly IConfigProvider configProvider; 

    public MaxLengthActionFilter(IConfigProvider configProvider) 
    { 
     if (configProvider == null) 
      throw new ArgumentNullException("configProvider"); 
     this.configProvider = configProvider; 
    } 

    public void OnActionExecuted(ActionExecutedContext filterContext) 
    { 
     var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor); 
     if (attribute != null) 
     { 
      var maxLength = attribute.MaxLength; 

      // Execute your behavior here, and use the configProvider as needed 
     } 
    } 

    public void OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor); 
     if (attribute != null) 
     { 
      var maxLength = attribute.MaxLength; 

      // Execute your behavior here, and use the configProvider as needed 
     } 
    } 

    public MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor) 
    { 
     MaxLengthAttribute result = null; 

     // Check if the attribute exists on the controller 
     result = (MaxLengthAttribute)actionDescriptor 
      .ControllerDescriptor 
      .GetCustomAttributes(typeof(MaxLengthAttribute), false) 
      .SingleOrDefault(); 

     if (result != null) 
     { 
      return result; 
     } 

     // NOTE: You might need some additional logic to determine 
     // which attribute applies (or both apply) 

     // Check if the attribute exists on the action method 
     result = (MaxLengthAttribute)actionDescriptor 
      .GetCustomAttributes(typeof(MaxLengthAttribute), false) 
      .SingleOrDefault(); 

     return result; 
    } 
} 

И ваш атрибут , который не должен содержать какое-либо поведение должен выглядеть примерно так:

// This attribute should contain no behavior. No behavior, nothing needs to be injected. 
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)] 
public class MaxLengthAttribute : Attribute 
{ 
    public MaxLengthAttribute(int maxLength) 
    { 
     this.MaxLength = maxLength; 
    } 

    public int MaxLength { get; private set; } 
} 

С более слабо связанный дизайн, тестирование на наличие атрибута гораздо более прямолинейно.

[TestMethod] 
public void Base_controller_must_have_MaxLengthFilter_attribute() 
{ 
    var att = typeof(BaseController).GetCustomAttribute<MaxLengthAttribute>(); 
    Assert.IsNotNull(att); 
} 
+1

Спасибо @ NightOwl888! Это действительно ответ, на который я могу погрузить свои зубы - и легко понять. Я всегда соглашался с тем, что атрибуты не должны определять поведение, но мое ограниченное время для интроспекции заставило меня сделать исключение для атрибутов фильтра действий. – ProfK

-1

Может быть, вы можете добавить действительный конфигурационный файл в тестовом проекте через «добавить файл в виде ссылки» enter image description here enter image description here

+0

Исходя из зависимости от внешнего источника конфигурации, это только моя абсолютная последняя опция. – ProfK

-2

Недавно я здесь все больше и больше вопросов относительно конфигурации «проблемы». Все они имеют общую базу - у вас есть несколько проектов, серверов, сервисов, которые должны использовать одну и ту же конфигурацию. Мой совет вам - прекратите использование Web.config.

Поместите всю свою конфигурацию в базу данных! Добавьте таблицу (или несколько таблиц) со всеми вашими конфигурационными ключами значения и прочитайте их при запуске приложения (global.asax).

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

+0

И как мне заставить IIS читать из моей базы данных? Проблема здесь вовсе не в конфигурации, но с отсутствием GetCustomAttribute, которая принимает аргументы конструктора. Вся дискуссия о конфиге - это другое, отдельное дело. – ProfK

+0

Web.Config - это совершенно приемлемое место для хранения ваших значений конфигурации и не является сложным. Вы можете вводить значения в классы и настраивать их для каждой среды без каких-либо проблем. – Spikeh

Смежные вопросы