Programing

단위 테스트 ASP.Net MVC Authorize 특성으로 로그인 페이지로의 리디렉션 확인

lottogame 2020. 11. 24. 07:31
반응형

단위 테스트 ASP.Net MVC Authorize 특성으로 로그인 페이지로의 리디렉션 확인


이것은 아마도 다른 한 쌍의 눈이 필요한 경우가 될 것입니다. 뭔가 빠진 게 틀림없지 만 왜 이런 종류의 것을 테스트 할 수 없는지 알 수 없습니다. 기본적으로 컨트롤러에 [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);
}

내가 찾고있는 RedirectResult는 사용자가 로그인 양식으로 리디렉션되고 있음을 나타내는 일종의 표시이지만 대신 ViewResult가 항상 반환되고 디버깅 할 때 사용자가 성공하더라도 Index () 메서드가 성공적으로 적중 된 것을 볼 수 있습니다. 인증되지 않았습니다.

내가 뭘 잘못하고 있니? 잘못된 수준에서 테스트합니까? 차라리 이런 종류의 경로 수준에서 테스트해야합니까?

페이지를 회전 할 때 로그인 화면이 실제로 강제로 표시되기 때문에 [Authorize] 속성이 작동한다는 것을 알고 있습니다.하지만 테스트에서이를 확인하려면 어떻게해야합니까?

컨트롤러 및 인덱스 방법은 동작을 확인할 수 있도록 매우 간단합니다. 완전성을 위해 포함했습니다.

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

감사합니다 ...


잘못된 수준에서 테스트하고 있습니다. [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");
    }
}

글쎄, 당신은 잘못된 수준에서 테스트하고 있을지 모르지만 그 테스트는 의미가 있습니다. 즉, 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");

글쎄요, 쉽지 않고 10 시간이 걸렸지 만 여기 있습니다. 나는 누군가가 그것으로부터 이익을 얻거나 나를 다른 직업으로 가도록 설득하기를 바랍니다. :) (BTW-나는 rhino mock을 사용하고 있습니다)

[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;
}

So that gets you confirmation that users not in a role don't have access. I tried writing a test to confirm the opposite, but after two more hours of digging through mvc plumbing I will leave it to manual testers. (I bailed when I got to the VirtualPathProviderViewEngine class. WTF? I don't want anything to do a VirtualPath or a Provider or ViewEngine much the union of the three!)

I am curious as to why this is so hard in an allegedly "testable" framework.


Why not just use reflection to look for the [Authorize] attribute on the controller class and / or the action method you are testing? Assuming the framework does make sure the Attribute is honored, this would be the easiest thing to do.


I don't agree with Dylan's answer, because 'user must be logged in' does not imply that 'controller method is annotated with AuthorizeAttribute'

to ensure 'user must be logged in' when you call the action method, the ASP.NET MVC framework does something like this (just hold on, it will get simpler eventually)

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 

It is hard to implement this pseudo script in a working c# code. Likely, Xania.AspNet.Simulator makes it really simple to setup a test like this and performs exactly these step under the cover. here is an example.

first install the package from nuget (version 1.4.0-beta4 at the time of writing)

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

Then your test method could look like this (assuming NUnit and FluentAssertions are installed):

[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(); 
}

참고URL : https://stackoverflow.com/questions/669175/unit-testing-asp-net-mvc-authorize-attribute-to-verify-redirect-to-login-page

반응형