Тестирование модуля 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);
}

RedirectResult, который я ищу, является некоторым признаком того, что пользователь перенаправляется в форму входа, но вместо этого ViewResult всегда возвращается, и при отладке я вижу, что метод Index() успешно ударил, хотя пользователь не аутентифицирован.

Я что-то делаю неправильно? Тестирование на неправильном уровне? Должен ли я лучше тестировать на уровне маршрута для такого рода вещей?

Я знаю, что атрибут [Authorize] работает, потому что, когда я разворачиваю страницу, экран входа в систему действительно навязывается мне, но как я могу проверить это в тесте?

Метод контроллера и индекса очень прост, так что я могу проверить поведение. Я включил их для полноты:

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

Любая помощь ценится...

Ответ 1

Вы тестируете на неправильном уровне. Атрибут [Авторизовать] гарантирует, что механизм маршрутизации никогда не вызовет этот метод для неавторизованного пользователя - RedirectResult будет на самом деле поступать с маршрута, а не из вашего метода контроллера.

Хорошая новость - там уже проверяют покрытие для этого (как часть исходного кода среды MVC), поэтому я бы сказал, что вам не нужно беспокоиться об этом; просто убедитесь, что ваш метод контроллера делает правильную вещь, когда он вызван, и доверяйте фреймворку, чтобы не называть его в неправильных обстоятельствах.

EDIT: если вы хотите проверить наличие атрибута в модульных тестах, вам нужно будет использовать отражение, чтобы проверить методы контроллера следующим образом. В этом примере будет проверяться наличие атрибута Authorize в методе POST ChangePassword в демонстрации "Новый проект ASP.NET MVC 2 Project", установленный с MVC2.

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

Ответ 2

Хорошо, что вы можете тестировать на неправильном уровне, но его тест имеет смысл. Я имею в виду, если я отмечаю метод с атрибутом 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;
}

Таким образом, вы получите подтверждение того, что пользователи, не имеющие роли, не имеют доступа. Я попробовал написать тест, чтобы подтвердить обратное, но после двух часов работы в mvc-сантехнике я оставлю его для ручных тестеров. (Я поручил, когда я добрался до класса VirtualPathProviderViewEngine.WTF? Я не хочу, чтобы что-либо делало VirtualPath или Провайдера или ViewEngine в целом из трех!)

Мне любопытно, почему это так сложно в якобы "проверяемой" структуре.

Ответ 3

Почему бы просто не использовать отражение, чтобы искать атрибут [Authorize] в классе контроллера и/или метод действия, который вы тестируете? Предполагая, что фреймворк удостоверился, что атрибут удостоен чести, это было бы самым легким делом.

Ответ 4

Я не согласен с ответом Дилана, потому что "пользователь должен войти в систему" ​​не означает, что "метод контроллера аннотируется с AuthorizeAttribute"

чтобы "пользователь должен был войти в систему", когда вы вызываете метод действия, инфраструктура 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 

Трудно реализовать это псевдо script в рабочем коде С#. Вероятно, Xania.AspNet.Simulator позволяет очень просто настроить такой тест и выполнить именно этот шаг под обложкой. вот пример.

сначала установите пакет из nuget (версия 1.4.0-beta4 на момент написания)

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

Тогда ваш тестовый метод может выглядеть так (при условии установки 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(); 
}