2014-12-02 52 views
5

我想測試我的基礎控制器是用特定的動作過濾器裝飾的。由於此過濾器的構造函數看起來爲web.config,因此我的第一次嘗試測試失敗,因爲測試項目沒有有效的配置文件。繼續前進,我使用了一個TestConfigProvider,我注入到過濾器構造函數中,但以下測試失敗,因爲配置提供程序未傳遞給構造函數。我還可以測試該過濾器是否適用?如何使用構造函數參數測試Action Filter的存在?

[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。這對您的測試方法不是問題,這是過濾器設計方法的一個問題。

正如在帖子中指出的那樣,您可以通過將動作過濾器屬性分成兩部分來解決此問題。

  1. 一個不包含標記控制器和操作方法行爲的屬性。
  2. 一個DI友好的類,實現IActionFilter幷包含所需的行爲。

該方法是使用IActionFilter來測試該屬性的存在,然後執行所需的行爲。動作過濾器可以提供所有依賴關係,然後在構建應用程序時注入。

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

注:如果您需要任何過濾器的依賴過一輩子比單身更短,你將需要使用一個GlobalFilterProviderthis 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 2014-12-14 16:59:55

-1

也許你可以通過「添加文件鏈接」 enter image description here enter image description here

+0

訴諸依賴於外部配置源只是我絕對的最後一個選項。 – ProfK 2014-12-13 11:39:18

-2

最近我在這裏越來越多的關於問題的配置「問題」的有效配置文件添加到您的測試項目。他們都有一個共同的基礎 - 你有幾個項目,服務器,需要使用相同配置的服務。我的建議 - 停止使用Web.config。

將所有配置放入數據庫! 添加一個包含所有配置鍵的表(或者幾個表)的值並在應用程序啓動時讀取它們(global.asax)。

這樣您就不必擔心應對每個項目的配置或將其注入到不同的構造函數中。

+0

我該如何讓IIS從我的數據庫中讀取?這裏的問題與配置無關,但沒有帶構造函數參數的GetCustomAttribute。整個配置辯論是另一個獨立的問題。 – ProfK 2014-12-13 16:00:56

+0

Web.Config是一個完全可以接受的地方來存儲你的配置值,而且非常複雜。您可以將值注入到類中,並在沒有任何麻煩的情況下在每個環境中配置它們。 – Spikeh 2017-06-19 13:02:56