Eine sehr nützliche Sache in ASP.NET MVC sind die ActionFilter, die zum Beispiel im Fall des Authorize-Attributs dafür sorgen, dass beim Zugriff auf einen ganzen Controller oder eine einzelne Action durch den Benutzer vorab geprüft wird, ob dieser Zugriff überhaupt gestattet ist, d.h. im einfachsten Fall, ob der User authentifiziert, also eingeloggt ist.
Nun macht es nach meiner Meinung keinen Sinn diese Filter bzw. ihre Auswirkungen auf das Verhalten des Controllers zu testen, da es sich dabei - genau wie bei Zugriffen auf DateTime, File-System o.ä. - um Infrastruktur-Code des Frameworks handelt, auf dessen Funktionieren man schlicht vertrauen kann. Das heißt es ist unnötig für eine Controller-Action, die mit dem Authorize-Filter versehen ist, zu prüfen, ob zum Login weitergeleitet wird, wenn der User im aktuellen Kontext nicht eingeloggt ist.
Doch es gibt einen ganz elementareren Fall, der in meinen Augen durchaus testenswert ist: ob die Filter überhaupt gesetzt sind. Das tut nicht weh und vermeidet später böse Überraschungen, die mitunter teuer werden können (vor allem bei Login-Prüfungen = Sicherheit!). Denn insbesondere wenn man vollständig test-getrieben entwickelt vergisst man dann mal leicht das ein oder andere Attribut zu setzen. Daher besser gleich mit in die Tests einbauen.
Damit das wie gesagt recht einfach funktioniert habe ich mir zwei kleine Erweiterungen geschrieben:
1: public static class ActionFilterAssertions
2: {
3:
4: public static void AssertFilterIsSetForController(
5: this Controller controller, Type filterType)
6: {
7: Assert.IsTrue(Attribute.IsDefined(controller.GetType(), filterType));
8: }
9:
10: public static void AssertFilterIsSetForAction<T>(this T controller,
11: Expression<Func<T, object>> expression, Type filterType)
12: {
13: var attributes = ((MethodCallExpression)expression.Body).
14: Method.GetCustomAttributes(filterType, false);
15: Assert.AreNotEqual(0, attributes.Length);
16: }
17:
18: }
Sieht komplizierter aus, als es ist. Im Grunde wird tatsächlich nur geprüft, ob ein Attribut auf Klassen-Ebene (Controller) oder Methoden-Ebene (Action) gesetzt ist.
Beispiel für die Verwendung:
1: [Authorize]
2: public class TestController : Controller
3: {
4:
5: [ValidateAntiForgeryToken]
6: public ActionResult Test()
7: {
8: return View();
9: }
10:
11: }
12:
13: [TestFixture]
14: public class Test : ConcernOf<TestController>
15: {
16:
17: public override void Setup()
18: {
19: Sut = new TestController();
20: }
21:
22: [Test]
23: public void AuthorizeAttributeIsSet()
24: {
25: Sut.AssertFilterIsSetForController(typeof(AuthorizeAttribute));
26: }
27:
28: [Test]
29: public void AntiForgeryTokenAttributeIsSet()
30: {
31: Sut.AssertFilterIsSetForAction(c => c.Test(),
32: typeof(ValidateAntiForgeryTokenAttribute));
33: }
34:
35: }