Ich habe ein paar Extension-Methods für schönere Unit-Tests vorgestellt. Nun stelle ich wieder zwei vor. Diese sind speziell für das ASP.NET MVC Framework mit der ASP.NET MVC Futures (Microsoft.Web.Mvc.Dll) Erweiterung.
public static class MvcBDDExtension
{
public static void should_link_to<T>(this Expression<Action<T>> expected, Expression<Action<T>> action) where T : Controller
public static void should_route_to<T>(this ActionResult actionResult, Expression<Action<T>> action) where T : Controller
}
Damit kann man überprüfen ob ein typisierter Link zu einer bestimmten Controller-Action gesetzt ist und ob ein RedirectRouteResult zu einer bestimmte Controller-Action zurückgegeben worden ist. Den Quelltext gibt es am Ende.
Erst einmal ein paar erklärende Worte vorneweg.
Typisierte Links sind mit den ASP.NET MVC Futures möglich. Diese haben den Vorteil dass ich die Controller als auch die Actions per Refactoring umbenennen kann ohne das mir die Anwendung kaputt geht. Man kann die Textuelle Verlinkung zwar mit Unit-Tests überprüfen, jedoch sollte ein Umbenennen einer Klasse oder Methode kein Anpassung von Unit-Tests zur Folge haben.
So sieht die Erstellung von typisierten Links innerhalb eines Views aus, am konkreten Beispiel einer MasterPage.
<div id="menucontainer">
<ul id="menu">
<li>
<%=Html.ActionLink(Model.LinkToHome, @"Startseite")%></li>
<li>
<%=Html.ActionLink(Model.LinkToAboutUs, @"Impressum")%></li>
</ul>
</div>
Das ViewModel sieht in diesem Fall so aus.
using System;
using System.Linq.Expressions;
namespace Newsletter.Web.Controllers.ModelViewData
{
public class ViewDataBase
{
public Expression<Action<AccountController>> LinkToLogout;
public Expression<Action<AdminController>> LinkToAdmin;
public Expression<Action<AccountController>> LinkToRegister;
public Expression<Action<HomeController>> LinkToHome;
public Expression<Action<HomeController>> LinkToAboutUs;
public string Title { get; set; }
public string Username { get; set; }
}
}
Der View ist entsprechend typisiert und bietet eine Model-Property zum einfacheren Zugriff auf das ViewModel.
using System;
using System.Web.Mvc;
using Newsletter.Web.Controllers.ModelViewData;
namespace Newsletter.Web.Views.Shared
{
public partial class Site : ViewMasterPage<ViewDataBase>
{
protected ViewDataBase Model
{
get { return ViewData.Model; }
}
}
}
Die Controller-Actions werden dann folgendermaßen Eingetragen. Es sind verschiedene Controller mit entsprechenden Actions.
public static class ViewDataExtensions
{
public static void SetMasterPageData(this ViewDataBase viewData)
{
viewData.LinkToAdmin = ac => ac.Index();
viewData.LinkToRegister = ac => ac.Register(string.Empty, string.Empty, string.Empty, string.Empty);
viewData.LinkToLogout = ac => ac.Logout();
viewData.LinkToHome = hc => hc.Index();
viewData.LinkToAboutUs = hc => hc.About();
}
}
Damit sind typisierte Links auf Controller-Actions gesetzt und ich brauche mir keine Gedanken zu machen was passiert wenn ich diese umbenenne.
Es ist jedoch erforderlich in einem Unit-Test zu überprüfen ob auch wirklich bei jedem View die entsprechenden Links im ViewModel gesetzt sind.
Hier schlägt nun die Stunde für die should_link_to<T>() Extension-Method.
[Observation]
public void the_MasterPageLinks_should_be_set_if_a_ViewResult_is_given_back()
{
if (result is ViewResult)
{
var model = GetViewModel<ViewDataBase>();
model.LinkToAboutUs.should_link_to(homeController => homeController.About());
model.LinkToHome.should_link_to(homeController => homeController.Index());
model.LinkToAdmin.should_link_to(adminController => adminController.Index());
model.LinkToLogout.should_link_to(accountController => accountController.Logout());
model.LinkToRegister.should_link_to(accountController => accountController.Register(string.Empty, string.Empty, string.Empty, string.Empty));
}
}
Durch einfaches Lesen des Quelltextes kann man nun schnell erkennen was dazu führen muss um den Test zum funktionieren zu bringen.
result ist ein field innerhalb der Test-Klasse welchen den ActionResult der zu testenden Controller-Action beinhaltet und GetViewModel<T>() holte das aktuelle ViewModel aus dem result.
Möchte man jedoch Testen ob eine Weiterleitung zu einer bestimmten Controller-Action vorgenommen wurde. So ist dies mit should_route_to<T>() möglich.
using System;
using System.Web.Mvc;
using DerAlbert.UnitTest.Base.BDD;
using Newsletter.Manager;
using Newsletter.Web.Controllers;
using Newsletter.Web.Controllers.ModelViewData;
namespace Newsletter.Controllers.Tests.SpecsSubscriberController.AddNewSubscriber
{
[Concern(typeof (SubscriberController))]
public class When_the_user_request_the_newsletter_on_the_addnew_page : concern_of_SubscriberController
{
// einiges an Setup und Observations für das Beispiel weggelassen,
// da es sonst zu unübersichtlich als Beispiel wird.
private AddNewSubscriberViewData viewData;
protected override void establish_context()
{
base.establish_context();
viewData = new AddNewSubscriberViewData
{
SubscriberName = @"Hubert",
EMail = @"hello@world.de",
EMailVerify = @"hello@world.de",
Result = @"an Answer"
};
subscriberManager.when_told_to(sm => sm.ValidateCaptcha(viewData)).Return(true);
}
protected override void because()
{
result = sut.AddNewSubscriber(viewData);
}
[Observation]
public void the_page_should_be_redirected_to_SubscriberController_SubscriberAdded()
{
result.should_route_to<SubscriberController>(sc => sc.SubscriberAdded());
}
}
}
Dies ist ein Real-World BDD-Style Test und hier etwas abgespeckt dargestellt. Hier sieht man im Quelltext was passiert muss damit eine Spezifikation erfüllt ist.
Jetzt ist es soweit der Quelltext der Methoden. Diese brauchen die ASP.NET MVC Future und die vorhandenen BDD Extensions.
Happy Testing mit den Extension-Methods.
using System;
using System.Linq.Expressions;
using System.Web.Routing;
using Microsoft.Web.Mvc.Internal;
using System.Web.Mvc;
namespace DerAlbert.UnitTest.Base.BDD
{
public static class MvcBDDExtension
{
public static void should_link_to<T>(this Expression<Action<T>> expected, Expression<Action<T>> action) where T : Controller
{
RouteValueDictionary expectedDictionary = ExpressionHelper.GetRouteValuesFromExpression(expected);
RouteValueDictionary actionDictionary = ExpressionHelper.GetRouteValuesFromExpression(action);
actionDictionary.should_only_contain(expectedDictionary);
}
public static void should_route_to<T>(this ActionResult actionResult, Expression<Action<T>> action) where T : Controller
{
var redirectResult = (RedirectToRouteResult)actionResult;
RouteValueDictionary actionDictionary = ExpressionHelper.GetRouteValuesFromExpression(action);
actionDictionary.should_only_contain(redirectResult.Values);
}
}
}
Habt Ihr eigene Extension-Methods für Test? Her damit!
Wie haltet Ihr dass mit dem Testen?
Ist dies hier alles Mumpitz und Assert.IsXYZ() is viel besser?
Verehrte Leser, nutzt die Kommentarfunktion.
Technorati-Tags:
ASP.NET,
BDD,
Unit-Test
Print | posted on Saturday, December 06, 2008 12:57 PM
Der Eintrag ist mir etwas Wert