Модульное тестирование ASP.Net MVC Authorize атрибут для проверки перенаправления на страницу входа - PullRequest
62 голосов
/ 21 марта 2009

Вероятно, это просто случай, когда нужна еще пара глаз. Должно быть, я что-то упускаю, но я не могу понять, почему такого рода вещи нельзя проверить. Я в основном пытаюсь гарантировать, что неаутентифицированные пользователи не могут получить доступ к представлению, помечая контроллер атрибутом [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();
    }
}

Любая помощь приветствуется ...

Ответы [ 4 ]

97 голосов
/ 22 марта 2009

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

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

РЕДАКТИРОВАТЬ: Если вы хотите проверить наличие атрибута в ваших модульных тестах, вам нужно использовать отражение для проверки методов вашего контроллера следующим образом. В этом примере проверяется наличие атрибута Authorize в методе POST ChangePassword в демонстрационной версии «Новый проект ASP.NET MVC 2», установленной с 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");
    }
}
25 голосов
/ 10 марта 2011

Ну, вы можете тестировать не на том уровне, но это тест, который имеет смысл. Я имею в виду, что если я отмечаю метод с атрибутом 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 часов, но вот оно. Я надеюсь, что кто-то может извлечь из этого пользу или убедить меня пойти в другую профессию. :) (Кстати - я использую носорога)

[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, или Provider, или ViewEngine во многом объединением трех!)

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

4 голосов
/ 12 апреля 2009

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

2 голосов
/ 20 сентября 2015

Я не согласен с ответом Дилана, потому что «пользователь должен войти в систему» ​​не означает, что «метод контроллера аннотирован 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 

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

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

PM> установочный пакет 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(); 
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...