Unit test ASP.Net MVC Autorizza l'attributo per verificare il reindirizzamento alla pagina di accesso

Unit test ASP.Net MVC Autorizza l'attributo per verificare il reindirizzamento alla pagina di accesso

Stai testando al livello sbagliato. L'attributo [Autorizza] garantisce che il instradamento motore non invocherà mai quel metodo per un utente non autorizzato:il RedirectResult proverrà effettivamente dal percorso, non dal metodo del controller.

La buona notizia è che esiste già una copertura di test per questo (come parte del codice sorgente del framework MVC), quindi direi che non devi preoccuparti di questo; assicurati solo che il tuo metodo controller faccia la cosa giusta quando viene chiamato e fidati del framework per non chiamarlo nelle circostanze sbagliate.

EDIT:se vuoi verificare la presenza dell'attributo nei tuoi unit test, dovrai usare la riflessione per ispezionare i metodi del controller come segue. Questo esempio verificherà la presenza dell'attributo Authorize nel metodo POST ChangePassword nella demo "New ASP.NET MVC 2 Project" installata 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");
    }
}

Bene, potresti testare al livello sbagliato, ma è il test che ha senso. Voglio dire, se contrassegno un metodo con l'attributo authorize(Roles="Superhero"), non ho davvero bisogno di un test se l'ho contrassegnato. Quello che (credo di volere) è verificare che un utente non autorizzato non abbia accesso e che un utente autorizzato lo abbia.

Per un utente non autorizzato un test come questo:

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

Beh, non è facile e mi ci sono volute 10 ore, ma eccolo qui. Spero che qualcuno possa trarne beneficio o convincermi a intraprendere un'altra professione. :) (BTW - Sto usando 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();
}

Anche se non è molto utile senza questa funzione di supporto:

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

In questo modo si ottiene la conferma che gli utenti non in un ruolo non hanno accesso. Ho provato a scrivere un test per confermare il contrario, ma dopo altre due ore di scavo nell'impianto idraulico mvc lo lascerò ai tester manuali. (Ho salvato quando sono arrivato alla classe VirtualPathProviderViewEngine. WTF? Non voglio nulla per fare un VirtualPath o un Provider o ViewEngine tanto l'unione dei tre!)

Sono curioso di sapere perché questo è così difficile in un quadro presumibilmente "testabile".


Perché non usare semplicemente la riflessione per cercare il [Authorize] attributo sulla classe del controller e/o sul metodo di azione che stai testando? Supponendo che il framework si assicuri che l'attributo sia rispettato, questa sarebbe la cosa più semplice da fare.