unit testingASP.Net MVC授权属性validationredirect到login页面

这可能会变成只是需要另一双眼睛的情况。 我一定会错过一些东西,但是我不明白为什么这种东西不能被testing。 我基本上试图确保未经身份validation的用户不能访问该视图通过使用[Authorize]属性标记控制器,我试图用下面的代码来testing:

[Fact] public void ShouldRedirectToLoginForUnauthenticatedUsers() { var mockControllerContext = new Mock<ControllerContext>() { DefaultValue = DefaultValue.Mock }; var controller = new MyAdminController() {ControllerContext = mockControllerContext.Object}; mockControllerContext.Setup(c => c.HttpContext.Request.IsAuthenticated).Returns(false); var result = controller.Index(); Assert.IsAssignableFrom<RedirectResult>(result); } 

我正在寻找的RedirectResult是某种迹象表明用户正在被redirect到login表单,而是总是返回一个ViewResult和debugging时,我可以看到,即使用户是未经authentication。

难道我做错了什么? 在错误的级别testing? 我应该在路线上testing这种事情吗?

我知道[Authorize]属性正在工作,因为当我旋转页面时,login屏幕确实被强制在我身上 – 但是如何在testing中validation这一点?

控制器和索引方法非常简单,以便我可以validation行为。 我已经包括他们的完整性:

 [Authorize] public class MyAdminController : Controller { public ActionResult Index() { return View(); } } 

任何帮助赞赏…

你正在testing错误的水平。 [Authorize]属性确保路由引擎永远不会为未经授权的用户调用该方法–RedirectResult实际上将来自路由,而不是来自您的控制器方法。

好消息是,已经有testing报道(作为MVC框架源代码的一部分),所以我会说你不必担心; 只要确保你的控制器方法被调用的时候做了正确的事情,并且相信框架不会在错误的情况下调用它。

编辑:如果你想validationunit testing中属性的存在,你需要使用reflection来检查你的控制器方法,如下所示。 此示例将validation随MVC2一起安装的“新buildASP.NET MVC 2项目”演示中的ChangePassword POST方法上是否存在Authorize属性。

 [TestFixture] public class AccountControllerTests { [Test] public void Verify_ChangePassword_Method_Is_Decorated_With_Authorize_Attribute() { var controller = new AccountController(); var type = controller.GetType(); var methodInfo = type.GetMethod("ChangePassword", new Type[] { typeof(ChangePasswordModel) }); var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true); Assert.IsTrue(attributes.Any(), "No AuthorizeAttribute found on ChangePassword(ChangePasswordModel model) method"); } } 

那么你可能是在错误的水平testing,但它的testing是有道理的。 我的意思是,如果我用授权(Roles =“Superhero”)属性标记一个方法,如果我标记它,我并不需要testing。 我想(我想)是testing一个未经授权的用户没有访问权限,并授权用户。

对于未经授权的用户,可以这样testing:

 // Arrange var user = SetupUser(isAuthenticated, roles); var controller = SetupController(user); // Act SomeHelper.Invoke(controller => controller.MyAction()); // Assert Assert.AreEqual(401, controller.ControllerContext.HttpContext.Response.StatusCode, "Status Code"); 

好吧,这不容易,花了我10个小时,但在这里。 我希望有人能从中受益,或说服我进入另一个行业。 :)(顺便说一句 – 我正在使用犀牛模拟)

 [Test] public void AuthenticatedNotIsUserRole_Should_RedirectToLogin() { // Arrange var mocks = new MockRepository(); var controller = new FriendsController(); var httpContext = FakeHttpContext(mocks, true); controller.ControllerContext = new ControllerContext { Controller = controller, RequestContext = new RequestContext(httpContext, new RouteData()) }; httpContext.User.Expect(u => u.IsInRole("User")).Return(false); mocks.ReplayAll(); // Act var result = controller.ActionInvoker.InvokeAction(controller.ControllerContext, "Index"); var statusCode = httpContext.Response.StatusCode; // Assert Assert.IsTrue(result, "Invoker Result"); Assert.AreEqual(401, statusCode, "Status Code"); mocks.VerifyAll(); } 

虽然如果没有这个辅助函数,那不是很有用:

 public static HttpContextBase FakeHttpContext(MockRepository mocks, bool isAuthenticated) { var context = mocks.StrictMock<HttpContextBase>(); var request = mocks.StrictMock<HttpRequestBase>(); var response = mocks.StrictMock<HttpResponseBase>(); var session = mocks.StrictMock<HttpSessionStateBase>(); var server = mocks.StrictMock<HttpServerUtilityBase>(); var cachePolicy = mocks.Stub<HttpCachePolicyBase>(); var user = mocks.StrictMock<IPrincipal>(); var identity = mocks.StrictMock<IIdentity>(); var itemDictionary = new Dictionary<object, object>(); identity.Expect(id => id.IsAuthenticated).Return(isAuthenticated); user.Expect(u => u.Identity).Return(identity).Repeat.Any(); context.Expect(c => c.User).PropertyBehavior(); context.User = user; context.Expect(ctx => ctx.Items).Return(itemDictionary).Repeat.Any(); context.Expect(ctx => ctx.Request).Return(request).Repeat.Any(); context.Expect(ctx => ctx.Response).Return(response).Repeat.Any(); context.Expect(ctx => ctx.Session).Return(session).Repeat.Any(); context.Expect(ctx => ctx.Server).Return(server).Repeat.Any(); response.Expect(r => r.Cache).Return(cachePolicy).Repeat.Any(); response.Expect(r => r.StatusCode).PropertyBehavior(); return context; } 

因此,您可以确认不在angular色中的用户无法访问。 我试图写一个testing来确认相反的情况,但是在通过mvcpipe道挖掘两个小时之后,我将把它留给手动testing人员。 (当我到达VirtualPathProviderViewEngine类的时候,我得到了保护.WTF?我不想做任何事情来做一个VirtualPath或者一个Provider或者ViewEngine,这三者的联合!

我很好奇,为什么在一个所谓的“可testing的”框架中这是如此的困难。

为什么不使用reflection来查找控制器类上的[Authorize]属性和/或正在testing的操作方法? 假设框架确实确定了属性,这将是最简单的事情。

我不同意Dylan的回答,因为“用户必须login”并不意味着“控制器方法用AuthorizeAttribute注释”

为了确保'用户必须login',当你调用动作方法时,ASP.NET MVC框架就是这样做的(只要坚持下去,最终会变得更简单)

 let $filters = All associated filter attributes which implement IAuthorizationFilter let $invoker = instance of type ControllerActionInvoker let $ctrlCtx = instance or mock of type ControllerContext let $actionDesc = instance or mock of type ActionDescriptor let $authzCtx = $invoker.InvokeAuthorizationFilters($ctrlCtx, $filters, $actionDesc); then controller action is authorized when $authzCtx.Result is not null 

很难在工作的c#代码中实现这个伪脚本。 很可能, Xania.AspNet.Simulator使设置这样的testing非常简单,并在封面下执行这些步骤。 这里是一个例子。

首先在编写本文时从nuget(版本1.4.0-beta4)安装包,

PM> install-package Xania.AspNet.Simulator -Pre

那么你的testing方法可能是这样的(假设安装了NUnit和FluentAssertions):

 [Test] public void AnonymousUserIsNotAuthorized() { // arrange var action = new ProfileController().Action(c => c.Index()); // act var result = action.GetAuthorizationResult(); // assert result.Should().NotBeNull(); } [Test] public void LoggedInUserIsAuthorized() { // arrange var action = new ProfileController().Action(c => c.Index()) // simulate authenticated user .Authenticate("user1", new []{"role1"}); // act var result = action.GetAuthorizationResult(); // assert result.Should().BeNull(); }