Piękno ASP.NET MVC polega na tym, że prawie wszystko można wymienić, zamienić, przetestować… tyle tylko, że Ci goście z Microsoftu uwielbiają internal-e. Wszysko co się da dają jako internal a może nawet więcej. No i tyle byłoby z testowalności, ale do rzeczy.
Piszę własny ControllerFactory ale nie zupełnie od zera. Chcę standardowej funkcjonalności ale też chcę ładować jak pluginy z luźnych dll-ek. Nic prostszego, wystarczy trochę zmodyfikować DefaultControllerFactory i jeśli standardowe GetControllerType nie znajdzie odpowiedniego kontrolera to chcę po swojemu poszukać go w osobnych assembly. PseudoControllerFactory, który realizuje taką funkcjonalność jak na rycinie poniżej:
[csharp]
public class CustomControllerFactory : DefaultControllerFactory
{
public override IController CreateController(RequestContext requestContext, string controllerName)
{
var controllerType = GetControllerType(requestContext, controllerName) ??
GetPluginControllerType(requestContext, controllerName);
return controllerType != null ? GetControllerInstance(requestContext, controllerType) : null;
}
private Type GetPluginControllerType(RequestContext requestContext, string controllerName)
{
return controller_type_from_external_assembly(controllerName);
}
}
[/csharp]
Tylko jak to to przetestować? Trzeba jakoś tak spreparować RequestContext, żeby domyślny GetControllerType zwrócił null… no i zaczynają się schody, a to „This method cannot be called during the application’s pre-start initialization stage”, a to jakiś null reference exception a to jeszcze coś innego. Ściągnąłem źródła do MVC4 ale też nic nie pomogło, bo to co bym chciał nadpisać, zamockować czy jeszcze jakoś inaczej zaczarować to internal. Tak zupełnie przy okazji dowiedziałem się jak mvc sobie cacheuje informacje o controllerach – ciekawe czy mi się to przyda.
No i nagle… EUREKA GetControllerType jest internal protected więc walnę sobie jeszcze jedną klaskę dla czytelności tym razem w testach:
[csharp]
public class CustomControllerFactoryUnderTest : CustomControllerFactory
{
protected override Type GetControllerType(RequestContext requestContext, string controllerName)
{
return null;
}
}
[/csharp]
Teraz wszystkie testy zamiast na CustomControllerFactory piszę na CustomControllerFactoryUnderTest ale za to mam pewność, że GetControllerType zwróci nulla (i to bez mocków) no i reszta testów jakoś się już potoczy. W sumie to ten internal nie taki zły (przecież mogli dać private)
*) w kwestii formalnej GetControllerType jest protected internal dzięki czemu można ją nadpisać w innym assembly.