Как я могу проверить наличие фильтра действий с аргументами конструктора?

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

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

3 ответа

Решение

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

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

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

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

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

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

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

Реализация 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);
}

Возможно, вы можете добавить действительный файл конфигурации в ваш тестовый проект через "Добавить файл как ссылку"

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

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

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

Другие вопросы по тегам