Posty otagowaneASP.NET MVC
Testowanie konfiguracji routingu w ASP.NET MVC
Jedną z największych zalet architektury ASP.NET MVC jest prostota pisania testów jednostkowych, co umożliwia łatwą pracę tym wszystkim, którzy stosują TDD. Zdarzają się jednak przypadki, w których wymagana jest od programisty znajomość „zakamarków” frameworka w celu napisania, wydawałoby się prostego, testu. Jednym z takich przypadków jest testowanie routingu zapytań do odpowiedniej akcji kontrolera.
Głównym problemem jest sprzężenie komponentów routingu z klasą HttpContext. Klasa HttpContext moim zdaniem jest zmorą w przypadku wszystkich testów jednostkowych funkcjonalności wykorzystujących klasy z przestrzeni System.Web. W przeszłości, pisząc aplikacje oparte o ASP.NET Web Forms zmuszony byłem do tworzenia klasy adoptującej HttpContext, którą w prosty sposób potrafiłem zastąpić na potrzeby testów obiektami Test Double. W ASP.NET MVC problem ten został wyeliminowany poprzez wprowadzenie klasy HttpContextBase. Mimo to nadal pozostaje problem z tym, że klasa ta posiada zbyt wiele metod i właściwości zwracających często nam potrzebne obiekty. Najczęściej chodzi tutaj o obiekty klas HttpResponse i HttpRequest. Klasy te, podobnie jak klasa HttpContext, posiadają swoje odpowiedniki pozwalające na łatwiejsze testowanie – HttpResponseBase i HttpRequestBase.
To wszystko prowadzi do tego, że testowanie routingu w ASP.NET MVC wymaga z naszej strony dużego nakładu pracy by przygotować odpowiednio obiekty potrzebne do wykonania się testu. Na dodatek to nie koniec problemów. Wykorzystując routing w ASP.NET MVC, jako programiści nie jesteśmy zmuszeni do pisania skomplikowanej logiki, gdyż jedyne co musimy zrobić, to dodać jedną linię kodu domyślnie w klasie Application (global.asax) i już nasze zapytanie HTTP zostanie przekierowane do odpowiedniej akcji. W testach już tak łatwo nie jest, dlatego, że nie jesteśmy w stanie w prosty sposób sprawdzić, która z metod, będąca naszą akcją, zostanie wywołana. Aby dojść do sposobu pozwalającego na sprawdzenie wyniku, wymagane od nas jest zapoznanie się z operacjami wchodzącymi w skład routingu zapytania, które zostały przeze mnie opisane w jednym z poprzednich postów, oraz z paroma innymi klasami takimi jak ControllerActionInvoker czy MvcHandler.
W efekcie końcowym stworzymy test podobny do tego jak ten, poniżej, który weryfikują poprawność domyślnej konfigurację routingu.
//routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" }
[Test]
public void should_route_to_the_home_page()
{
var httpContext = MockRepository.GenerateStub();
var httpRequest = MockRepository.GenerateStub();
httpContext.Stub(x => x.Request).Return(httpRequest);
string testedRequestUrl = "~/Home";
httpRequest.Stub(x => x.AppRelativeCurrentExecutionFilePath).Return(testedRequestUrl);
var routeData = RouteTable.Routes.GetRouteData(httpContext);
var controllerName = routeData.GetRequiredString("controller");
var actionName = routeData.GetRequiredString("action");
Assert.IsTrue(string.Equals(controllerName, "Home",StringComparison.OrdinalIgnoreCase));
Assert.IsTrue(string.Equals(actionName, "Index", StringComparison.OrdinalIgnoreCase));
}
W sumie okazuje się, że test nie jest znów tak trudny jak to się zapowiadało, ale… Zawsze musi być jakieś, ale:). Powyższy test może stwarzać problemy w trakcie refaktoryzacji. Kryteria powyższego testu polegają na porównywaniu ciągu znaków określających nazwę akcji (metody) i nazwę klasy kontrolera, który posiada daną akcję. Problem leży w tym, że ciągi te nie zostaną automatycznie zastąpione przez narzędzia refaktoryzujące takie jak naprzykład Resharper w przypadku zmiany nazwy akcji lub kontrolera.
Istnieje jednak alternatywa i jest ona dostępna w ramach projektu MVC Contrib. Biblioteka MvcContrib.TestHelper wchodząca w skład tego projektu posiada klasę RouteTestingExtensions z metodami rozszerzającymi pozwalającymi w bardzo prosty sposób dokonać odrobinę magii opartej o lambda expressions, co w efekcie pozwoli nam pisać testy takie jak ten poniższy.
[Test]
public void should_route_to_the_home_page()
{
"~/home".Route().ShouldMapTo(h => h.Index());
}
Jak widać test ten jest banalnie prosty i nie wymaga z naszej strony prawie że żadnego nakładu pracy, a co najważniejsze każdy jest w stanie rozpocząć przygodę z testowaniem przez siebie zdefiniowanych route.
1 comment Luty 20, 2009
Pierwsze spojrzenie na ASP.NET MVC – od wnętrza
Główna zaletą a jednocześnie wadą frameworków udostępnionych przez Microsoft jest to, że ich domyślna konfiguracja pozwala na bezproblemowe rozpoczęcie z nimi przygody. Prowadzi to do tego, że aplikacje oparte o te frameworki, wdrożone na środowisku produkcyjnym często korzystają z zbędnych funkcjonalności. Tak też jest z ASP.NET MVC.
Plik web.config nowo stworzonego projektu ASP.NET MVC posiada w większości przypadków niepotrzebne rejestracje modułów i handlerów, pozwalających przykładowo na korzystanie z ASP.NET Ajax. Ponieważ poznając każdą nową technologię staram się “przegryźć” prze jej wnętrze, pierwszym z moich kroków w przypadku ASP.NET MVC było przejrzenie kodu, który jest odpowiedzialnie za podstawową realizację zapytania. Po krótkiej analizy wywnioskowałem, że jedynym elementem, który jest rzeczywiście wymagany w pliku web.config do uruchomienia aplikacji ASP.NET MVC jest moduł System.Web.Routing.UrlRoutingModule. Dlatego w zależności od trybu puli aplikacji, na której hostowana jest nasza aplikacja, wymagane jest pozostawienie następujących wpisów w pliku web.config:
- w przypadku managed pipeline mode: Integrated (dostępny od wersji 7.0 usługi IIS)
<configuration>
<system.webServer>
...
<modules runAllManagedModulesForAllRequests="true">
<remove name="UrlRoutingModule"/>
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</modules>
</system.webServer>
</configuration>
- w przypadku managed pipeline mode: Classic
<configuration>
<system.web>
...
<httpModules>
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</httpModules>
</system.web>
</configuration>
Więcej na temat możliwości i różnic wyżej przedstawionych trybów puli aplikacji można znaleźć tutaj.
Wszystko super, ale po co nam ten UrlRoutingModule?
Aby to zrozumieć należy zapoznać się z zasadą działania wzorca Front Controller, który został zastosowany w ASP.NET MVC. Front Controller jest wzorcem często stosowany w aplikacjach webowy. W jego odpowiedzialności leży obsługiwanie wszystkich zapytań przychodzących do aplikacji i wywoływania komend(operacji) odpowiadającym tym zapytaniom.

- Działanie wzorca Front Controller źródło: http://i.msdn.microsoft.com/ms978723.Des_FrontController_Fig02(en-us,MSDN.10).gif
Powyższy diagram przedstawia standardowy scenariusz obsługiwania zapytania przy pomocy Front Controllera .
Klient wysyła zapytanie, które jest obsługiwane przez Handler. Handler dokonuje operacji wspólnych do wszystkich zapytań a następnie na podstawie adresu URL decyduje, którą z dostępnych komend(Command) wywołać. Komenda ta z kolei odpowiedzialna jest za wywołanie określonej logiki biznesowej i za przekierowanie do odpowiedniego widoku(View). Ten zaś na podstawie informacji przekazanych przez komendę, generuje stronę, która następnie zwracana jest do klienta.
Interesującym nas elementem w powyższym diagramie jest Handler, którego funkcjonalność w ASP.NET MVC częściowo realizowane jest właśnie przez UrlRoutingModule.
UrlRoutingModule odpowiedzialny jest za znalezienie obiektu typu RouteData, który przechowuje informacje o trasie odpowiadającej adresowi URL obsługiwanego zapytania. Trasa to nic innego jak szablon adresu URL, w którym można zdefiniować parametry. Na podstawie tych parametrów możliwe jest wyszukiwanie potrzebnych nam informacji z adresu URL obsługiwanego zapytania odpowiadającego wybranej trasie. Możliwe trasy są domyślnie definiowane w trakcie startu aplikacji, poprzez dodawanie ich do kolekcji RouteCollection w pliku global.asax. Ta kolekcja wykorzystywana jest właśnie przez UrlRoutingModule w celu wcześniej wspomnianego wyszukiwania trasy. Następnie wykorzystując znaleziony obiekt RouteData (dokładniej mówiąc obiekt implementujący interfejs IRoutingModule udostępniony przez RouteData), moduł UrlRoutingModule uzyskuje dostęp do obiektu implementującego interfejs IHttpHandler odpowiedzialnego za dalszą realizację zapytania.
W przypadku ASP.NET MVC jest to najczęściej obiekt klasy MvcHandler. Ta klasa realizuje pozostałe funkcjonalności Handlera w wzorcu Front Controller. Wywołuje ona odpowiednią komendę, wykorzystując do tego informacje udostępnione przez obiekt klasy RequestContext, który jest wstrzykiwany do obiektu MvcHandlera w trakcie jego konstrukcji. RequestContext posiada obiekt RouteData, który jak już wspominałem daje dostęp do informacji związanych z trasą. Jednym z najbardziej kluczowych informacji w tym przypadku jest parametr controller. Służy on do wyznaczenie klasy posiadającej komendę, która zostanie wykonana w celu zakończenia realizacji zapytania. To tłumaczy domyślną postać wzorców adresów URL przy rejestrowaniu tras w global.asax.
routes.MapRoute(
“Default”, // Route name
“{controller}/{action}/{id}”, // URL with parameters
new { controller = “Home”, action = “Index”, id = “” } // Parameter defaults
);
Jako, że najlepiej chyba do każdego z nas przemawia obraz a nie treść czytana, dołaczam poniżej diagram sekwencji. Diagram ten przedstawia wcześniej wspomnianą interakcję klas wykorzystywanych przez UrlRoutingModule. Pomija on jedynie aspekt związany z wybraniem odpowiedniej komendy przez klasę MvcHandler, dlatego, że jest to temat warty całego postu
.
1 comment Styczeń 16, 2009


