2009-03-21 5 views
56

Возможно, это будет случай, когда вам просто нужна другая пара глаз. Я, должно быть, что-то пропустил, но не могу понять, почему этот предмет не может быть проверен. Я в основном пытаюсь обеспечить, чтобы неаутентифицированных пользователи не могут получить доступ к мнению, пометив контроллер с [Authorize] атрибутом, и я пытаюсь к тестам это, используя следующий код:Тестирование модуля ASP.Net MVC Авторизовать атрибут для проверки перенаправления на страницу входа

[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] 
public class MyAdminController : Controller 
{ 
    public ActionResult Index() 
    { 
     return View(); 
    } 
} 

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

ответ

88

Вы тестируете на неправильном уровне. Атрибут [Авторизовать] гарантирует, что движок маршрутизации никогда не будет вызывать этот метод для неавторизованного пользователя - 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"); 
    } 
} 
+0

Thanks Dylan - Я думал, что могу тестировать на неправильном уровне. Я доволен идеей «предполагать», что если контроллер попадает, пользователь аутентифицируется. P.S. Вы уверены, что он протестирован в рамках? Я могу увидеть несколько тестов, поставляющих действительный IPrincipal, но ни один из них не проверяет неверный случай ;-) – RobertTheGrey

+2

Er, нет ... на самом деле не проверил этот тестовый пример; Я доверяю банде MVC, чтобы все было правильно. Виноват! –

+3

Мне нравится ответ, почему это неправильный подход, но я не уверен в аргументе «функция проверена в рамках и работает». Я верю, что атрибут работает правильно, это работа с каркасом, но я все равно хотел бы утверждать, какие методы моих контроллеров используют этот атрибут. – Mathias

3

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

+1

Здесь протестированы две разные вещи. (1) Проверьте, что пользовательский атрибут выполняет то, что он должен делать; и (2) что контроллер/действие остается украшенным атрибутом. Вы отвечаете на (2), но я думаю, что ссылка, размещенная Дарио Кинтаной, наилучшим образом отвечает на (1). –

+0

В реальном мире аннотация с атрибутом Authorize не является единственным способом авторизации запросов/действий контроллера. –

22

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

[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 в целом из трех!)

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

+0

WTF действительно, к счастью, если вы придерживаетесь этого, вы можете найти способ обойти его и вокруг всех последующих проблем после него, как и я. Возьмите loook в моем проекте github по адресу: https://github.com/ibrahimbensalah/Xania.AspNet.Simulator/blob/master/Xania.AspNet.Simulator.Tests/ –

+0

Это сообщение _almost полностью_ так же, как [ссылка ] (https://web.archive.org/web/20130213143434/http://darioquintana.com.ar/blogging/2009/05/23/aspnet-mvc-testing-a-custom-authorize-filters), упомянутые в сообщение от @ Dario. Вы сами это разработали? –

+0

Да, он развивается сам по себе, и все еще активно развивается. в настоящее время поддерживает mvc4 и mvc5 от авторизации, привязки моделей, проверки запроса, рендеринга бритвы .... –

1

Я не согласен с ответом Дилана, потому что «пользователь должен войти в» не означает, что «метод управления помечается 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(); 
} 
Смежные вопросы