2009-03-21 9 views
56

Esto probablemente sea un caso de solo necesitar otro par de ojos. Debo extrañar algo, pero no puedo entender por qué este tipo de cosas no pueden ser probadas. básicamente estoy tratando de asegurarse de que los usuarios no autenticados no pueden acceder a la vista marcando el controlador con el atributo [Autorizar] y yo estoy tratando de pruebas esta usando el siguiente código:Pruebas unitarias ASP.Net MVC Autorizar el atributo para verificar el redireccionamiento a la página de inicio de sesión

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

El RedirectResult Busco es algún tipo de indicación de que el usuario está siendo redirigido al formulario de inicio de sesión, pero en su lugar siempre se devuelve un ViewResult y cuando se depura puedo ver que el método Index() se ejecuta correctamente aunque el usuario no esté autenticado.

¿Estoy haciendo algo mal? Probando en el nivel equivocado? ¿Debería estar probando en el nivel de ruta para este tipo de cosas?

Sé que el atributo [Autorizar] está funcionando, porque cuando hago girar la página, la pantalla de inicio de sesión se me impone, pero ¿cómo puedo verificar esto en una prueba?

El controlador y el método de índice son muy simples solo para que pueda verificar el comportamiento. Yo los he incluido para la integridad:

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

Cualquier ayuda apreciada ...

Respuesta

88

caso está probando en el nivel equivocado. El atributo [Autorizar] garantiza que el motor de enrutamiento nunca invocará ese método para un usuario no autorizado; el resultado de RedirectResult vendrá de la ruta, no de su método de controlador.

La buena noticia es que ya hay cobertura de prueba para esto (como parte del código fuente del framework MVC), así que diría que no necesita preocuparse por eso; solo asegúrese de que su método de control haga lo correcto cuando se llame a, y confíe en que el marco no lo llame en las circunstancias incorrectas.

EDITAR: Si desea verificar la presencia del atributo en las pruebas de su unidad, deberá usar la reflexión para inspeccionar los métodos de su controlador de la siguiente manera. Este ejemplo verificará la presencia del atributo Authorize en el método ChangePassword POST en la demostración 'New ASP.NET MVC 2 Project' que se instala con 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

Gracias Dylan - pensé que podría estar poniendo a prueba en el nivel equivocado. Estoy contento con la idea de "suponer" que si el controlador recibe un golpe, el usuario se autentica. P.S. ¿Estás seguro de que está probado en el marco? Puedo ver algunas pruebas que proporcionan IPrincipal válido, pero ninguna que pruebe el caso no válido ;-) – RobertTheGrey

+2

Er, no ... no he verificado realmente el caso de prueba; Estoy confiando en que la pandilla de MVC lo hizo bien. ¡Mi error! –

+3

Me gusta la respuesta de por qué no es el enfoque correcto, pero no estoy convencido del argumento "la característica se prueba en el marco y funciona". Confío en que el atributo funciona correctamente, ese es el trabajo del framework, pero aún quisiera afirmar qué métodos de mis controladores usan el atributo. – Mathias

3

¿Por qué no utilizar la reflexión para buscar el atributo [Authorize] en la clase del controlador y/o el método de acción que se está probando? Suponiendo que el marco se asegure de que se respete el atributo, esto sería lo más fácil de hacer.

+1

Aquí se prueban dos cosas distintas. (1) Pruebe que un atributo personalizado hace lo que se supone que debe hacer; y (2) Que un controlador/acción queda decorado con el atributo. Estás respondiendo a (2) pero creo que el enlace publicado por Dario Quintana es la mejor respuesta a (1). –

+0

En el mundo real, la anotación con el atributo Autorizar no es la única forma utilizada para autorizar solicitudes/acciones de controlador. –

22

Bueno, es posible que esté probando en el nivel incorrecto, pero es la prueba que tiene sentido. Quiero decir, si marcó un método con el atributo authorize (Roles = "Superhero"), realmente no necesito una prueba si lo señalé. Lo que yo (creo que) quiero es probar que un usuario no autorizado no tiene acceso y que lo hace un usuario autorizado.

Para un usuario no autorizado de una prueba como esta:

// 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"); 

Bueno, no es fácil y que me llevó 10 horas, pero aquí está. Espero que alguien se pueda beneficiar de eso o me convenza de ir a otra profesión.:) (Por cierto - estoy usando simulacro de rinoceronte)

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

Aunque, eso no es muy útil sin esta función auxiliar:

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

Así que le consigue la confirmación de que los usuarios no en un papel de don' t tiene acceso. Traté de escribir una prueba para confirmar lo contrario, pero después de dos horas más de excavar a través de mvc plomería, lo dejo a los probadores manuales. (Rescaté cuando llegué a la clase VirtualPathProviderViewEngine. WTF? No quiero hacer nada con VirtualPath o Provider o ViewEngine con la unión de los tres!)

Tengo curiosidad de por qué esto es tan difícil en un marco supuestamente "comprobable".

+0

WTF de hecho, afortunadamente si te apegas a él puedes encontrar una forma de evitarlo y alrededor de todos los siguientes problemas, al igual que yo. Eche un vistazo a mi proyecto github en: https://github.com/ibrahimbensalah/Xania.AspNet.Simulator/blob/master/Xania.AspNet.Simulator.Tests/ –

+0

Esta publicación es _ casi del todo_ igual que el [enlace ] (https://web.archive.org/web/20130213143434/http://darioquintana.com.ar/blogging/2009/05/23/aspnet-mvc-testing-a-custom-authorize-filters) referido en la publicación de @Dario. ¿Desarrollaste esto tú mismo? –

+0

Sí, está desarrollado solo, y todavía está en desarrollo activamente. actualmente admite mvc4 y mvc5 a partir de autorizaciones, carpetas modelo, validación de solicitudes, representación con maquinilla de afeitar .... –

1

No estoy de acuerdo con la respuesta de Dylan, porque 'usuario debe iniciar sesión en el' no implica que 'método controlador está anotado con AuthorizeAttribute'

para asegurar 'el usuario debe estar conectado' cuando se llama al método de acción, el marco ASP.NET MVC hace algo como esto (solo espera, será más sencillo con el tiempo)

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 

es difícil de poner en práctica este script seudo en un código C# de trabajo. Probablemente, Xania.AspNet.Simulator hace que sea realmente simple configurar una prueba como esta y realice exactamente estos pasos debajo de la cubierta. Aquí hay un ejemplo.

primero instalar el paquete de Nuget (versión 1.4.0-beta4 en el momento de la escritura)

PM> instalar paquete Xania.AspNet.Simulator -Pre

Luego de la prueba método podría tener este aspecto (suponiendo NUnit y FluentAssertions están instalados):

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