2014-12-02 13 views
5

Próbuję przetestować, że mój kontroler podstawowy jest ozdobiony pewien filtr działania. Ponieważ konstruktor filtru wygląda na web.config, moja pierwsza próba testowania kończy się niepowodzeniem, ponieważ projekt testowy nie ma prawidłowego pliku konfiguracyjnego. Idąc dalej, użyłem TestConfigProvider, który wprowadzam do konstruktora filtru, ale następujący test się nie powiódł, ponieważ dostawca konfiguracji nie został przekazany do konstruktora. Jak inaczej mogę sprawdzić, czy ten filtr jest stosowany?Jak mogę przetestować obecność filtra działania z argumentami konstruktora?

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

Odpowiedz

22

Cóż, zrobiłeś dobry pierwszy krok, uznając, że Web.config jest po prostu kolejną zależnością i zawijanie go do narzędzia ConfigProvider do wstrzykiwania jest doskonałym rozwiązaniem.

Ale pojawia się problem z jednym z problemów projektowych MVC - mianowicie, że aby być przyjaznym dla DI, atrybuty powinny dostarczać tylko meta-dane, ale never actually define behavior. To nie jest problem z twoim podejściem do testowania, jest to problem związany z podejściem do projektu filtra.

Jak wskazano w poście, można obejść ten problem, dzieląc atrybut filtra działania na dwie części.

  1. Atrybut, który nie zawiera żadnego zachowania oznaczającego kontrolery i metody działania za pomocą.
  2. Przyjazna dla DI klasa implementująca IActionFilter i zawierająca pożądane zachowanie.

Podejście polega na użyciu IActionFilter w celu przetestowania obecności atrybutu, a następnie wykonania pożądanego zachowania. Filtr działania może być dostarczany ze wszystkimi zależnościami, a następnie wstrzykiwany po złożeniu aplikacji.

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

UWAGA: Jeśli potrzebujesz któregokolwiek z zależnościami filtra mieć żywotność krótsza niż Singleton, trzeba będzie użyć GlobalFilterProvider jak w this answer.

Realizacja MaxLengthActionFilter będzie wyglądać mniej więcej tak:

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

A Twój atrybut które nie powinny zawierać żadnych zachowań powinien wyglądać mniej więcej tak:

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

Mając na bardziej luźno powiązany projekt, testowanie istnienia atrybutu jest znacznie prostsze.

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

Dziękujemy @ NightOwl888! To jest rzeczywiście odpowiedź, w którą mogę zanurzyć zęby - i łatwo zrozumieć. Zawsze zgadzałem się, że atrybuty nie powinny definiować zachowania, ale mój ograniczony czas na introspekcję zmusił mnie do zrobienia wyjątku dla atrybutów filtra działania. – ProfK

-1

Może można dodać poprawny plik konfiguracyjny do projektu badawczego poprzez „dodaj plik jako link” enter image description here enter image description here

+0

Wykonywanie zależności od zewnętrznego źródła konfiguracji jest moją ostatnią opcją. – ProfK

-2

Niedawno tu coraz więcej „problemy” pytanie dotyczące config. Wszystkie mają wspólną podstawę - masz kilka projektów, serwerów, usług, które muszą korzystać z tej samej konfiguracji. Moja rada dla ciebie - przestań używać Web.config.

Umieść całą swoją konfigurację w bazie danych! Dodaj tabelę (lub kilka tabel) ze wszystkimi kluczami konfiguracyjnymi i odczytaj wartości przy uruchamianiu aplikacji (global.asax).

W ten sposób nie musisz się martwić o to, czy konfigurujesz konfigurację do każdego projektu, czy wstrzykujesz go różnym konstruktorom.

+0

A jak mogę odczytać IIS z mojej bazy danych? Problem tutaj nie występuje w konfiguracji, ale z brakiem GetCustomAttribute, który pobiera argumenty konstruktora. Cała debata na temat konfiguracji to kolejna, osobna sprawa. – ProfK

+0

Web.Config to doskonale akceptowane miejsce do przechowywania wartości konfiguracyjnych i nie jest skomplikowane. Możesz wstrzykiwać wartości do klas i konfigurować je dla środowiska bez żadnych kłopotów. – Spikeh

Powiązane problemy