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