单元测试ASP.Net MVC Authorize属性以验证重定向到登录页面

Rob*_*rey 63 asp.net-mvc

这可能只是需要另一双眼睛的情况.我必须遗漏一些东西,但我无法弄清楚为什么这种东西无法测试.我基本上试图通过使用[Authorize]属性标记控制器来确保未经身份验证的用户无法访问视图,并且我尝试使用以下代码对此进行测试:

[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);
}
Run Code Online (Sandbox Code Playgroud)

我正在寻找的RedirectResult是某种指示用户被重定向到登录表单,但总是返回一个ViewResult,并且在调试时我可以看到即使用户是,也成功命中了Index()方法未经过身份验证.

难道我做错了什么?测试在错误的水平?我是否应该在路线级别测试此类事情?

我知道[Authorize]属性正在运行,因为当我启动页面时,登录屏幕确实被强加给了我 - 但我如何在测试中验证这一点?

控制器和索引方法非常简单,以便我可以验证行为.我把它们包括在内是为了完整性:

[Authorize]
public class MyAdminController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}
Run Code Online (Sandbox Code Playgroud)

任何帮助赞赏...

Dyl*_*tie 98

您正在测试错误的级别.[Authorize]属性确保路由引擎永远不会为未经授权的用户调用该方法 - RedirectResult实际上将来自路由,而不是来自您的控制器方法.

好消息是 - 已经有测试覆盖(作为MVC框架源代码的一部分),所以我说你不需要担心它; 只是确保你的控制器方法被调用做正确的事情,并相信框架不要在错误的情况下调用它.

编辑:如果要验证单元测试中是否存在属性,则需要使用反射来检查控制器方法,如下所示.此示例将验证与MVC2一起安装的"新建ASP.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");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 呃,不......实际上我自己没有验证过这个测试用例; 我相信MVC团伙已经做对了.我的错! (3认同)
  • 我喜欢为什么它不是正确的方法的答案,但我不相信"该功能在框架中进行测试并且有效".我相信该属性工作正常,这是框架的工作,但我仍然想断言我的控制器的哪些方法使用该属性. (3认同)
  • 谢谢迪伦 - 我以为我可能在错误的级别进行测试。我对“假设”如果控制器被击中,用户就通过身份验证的想法感到满意。PS你确定它在框架中经过测试吗?我可以看到一些提供有效 IPrincipal 的测试,但没有一个测试无效的情况;-) (2认同)
  • “ [Authorize]属性可确保路由引擎永远不会为未经授权的用户调用该方法-RedirectResult实际上将来自路由,而不是来自您的控制器方法。”-这句话的所有内容都是错误的。路由发生在授权过滤器运行之前很久-[MVC生命周期](https://docs.microsoft.com/zh-cn/aspnet/mvc/overview/getting-started/lifecycle-of-an-aspnet-mvc-5 -应用)。授权失败时,“ AuthorizeAttribute”(注册为“ IAuthorizationFilter”)会提供“ RedirectResult”。 (2认同)

Dan*_*Eli 25

那么你可能正在测试错误的水平,但它的测试是有道理的.我的意思是,如果我使用authorize(Roles ="Superhero")属性标记一个方法,如果我标记它,我真的不需要测试.我(我想)想要的是测试未经授权的用户没有访问权限以及授权用户的访问权限.

对于未经授权的用户,测试如下:

// 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");
Run Code Online (Sandbox Code Playgroud)

好吧,这并不容易,我花了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();
}
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

这样可以确保不在角色中的用户无权访问.我尝试编写一个测试来确认相反的情况,但经过两个小时的mvc管道挖掘后,我将把它交给手动测试人员.(当我到达VirtualPathProviderViewEngine类时,我获得了保释.WTF?我不想做任何事情来做VirtualPath或Provider或ViewEngine这三者的结合!)

我很好奇为什么在一个据说"可测试"的框架中这么难.