如何使用构造函数参数测试Action Filter的存在?

Pro*_*ofK 6 .net c# asp.net-mvc

我试图测试我的基本控制器是否装饰了某个动作过滤器.因为这个过滤器的构造函数看起来web.config,我第一次尝试测试失败,因为测试项目没有有效的配置文件.继续,我使用了一个TestConfigProvider我注入到过滤器构造函数中,但是下面的测试失败了,因为配置提供程序没有传递给构造函数.如果应用此过滤器,我还能怎么测试?

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

Nig*_*888 26

好吧,你已经迈出了良好的第一步,认识到Web.config只是另一个依赖项并将其包装到ConfigProvider中以进行注入是一个很好的解决方案.

但是,你正在惹恼MVC的一个设计问题 - 即,对于DI友好,属性应该只提供元数据,但从不实际定义行为.这不是您的测试方法的问题,这是过滤器设计方法的问题.

正如帖子中所指出的,您可以通过将动作过滤器属性分为两部分来解决此问题.

  1. 一个属性,不包含用于标记控制器和操作方法的行为.
  2. 一个DI友好的类,它实现IActionFilter并包含所需的行为.

方法是使用IActionFilter测试属性的存在,然后执行所需的行为.可以为操作筛选器提供所有依赖项,然后在组合应用程序时注入.

IConfigProvider provider = new WebConfigProvider();
IActionFilter filter = new MaxLengthActionFilter(provider);
GlobalFilters.Filters.Add(filter);
Run Code Online (Sandbox Code Playgroud)

注意:如果您需要任何过滤器的依赖项,其生命周期短于单例,则需要GlobalFilterProvider此答案中使用as .

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

并且,不应包含任何行为的属性如下所示:

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

通过更松散耦合的设计,对属性存在的测试更加简单.

[TestMethod]
public void Base_controller_must_have_MaxLengthFilter_attribute()
{
    var att = typeof(BaseController).GetCustomAttribute<MaxLengthAttribute>();
    Assert.IsNotNull(att);
}
Run Code Online (Sandbox Code Playgroud)