ASP.NET MVC, Moq, and unit testing auth/auth

This is a short post about unit testing authentication and authorisation using the framework Authorize attribute. For the most recent project we have built behind the ASP.NET provider template implementation of the IMembershipService to meet very specific customer requirements, but this testing approach remains the same if you use built in.

It was clear pretty quickly that duplication was going to happen testing authentication and authorisation so I wanted to distill the test code as much as I could. I ended up writing two helper methods to check for redirection of an unauthenticated user, and a user not in a define role - the following the helper in full:

public class AuthenticationTests
{
    public static void UnAuthorizedUserRedirects(Controller controller, string actionName)
    {
        var user = new Mock<IPrincipal>();
        user.ExpectGet(u => u.Identity.IsAuthenticated).Returns(false);
        var context = new Mock<ControllerContext>();
        context.ExpectGet(c => c.Controller).Returns(controller);
        context.ExpectGet(c => c.HttpContext.User).Returns(user.Object);
        context.ExpectSet(c => c.HttpContext.Response.StatusCode).Callback(status => Assert.AreEqual(401, status)).Verifiable();
        controller.ActionInvoker.InvokeAction(context.Object, actionName);
        context.VerifyAll();
        user.VerifyAll();
    }
    public static void UserNotInRoleRedirects(Controller controller, string actionName, string roleName)
    {
        var user = new Mock<IPrincipal>();
        user.ExpectGet(u => u.Identity.IsAuthenticated).Returns(true);
        user.Expect(u => u.IsInRole(roleName)).Returns(false);
        var context = new Mock<ControllerContext>();
        context.ExpectGet(c => c.Controller).Returns(controller);
        context.ExpectGet(c => c.HttpContext.User).Returns(user.Object);
        context.ExpectSet(c => c.HttpContext.Response.StatusCode).Callback(status => Assert.AreEqual(401, status)).Verifiable();
        controller.ActionInvoker.InvokeAction(context.Object, actionName);
        context.VerifyAll();
        user.VerifyAll();
    }
}

 

Using Moq they set expectations on a Moq'ed IPrincipal and ControllerContext to ensure that the action sets an HTTP StatusCode of 401 (unauthorised); I won't go into too much detail, you can read the code, and it's actually surprisingly straight forward...

Usage is then dead easy and keeps the tests really clean- just spin up your controller and ask one of the actions to check - for example:

[Test]
public void UnAuthenticatedUserRequestForRevokeRedirects()
{
    Mock<IMembershipService> membershipService = new Mock<IMembershipService>();
    AccountAdminController controller = new AccountAdminController(membershipService.Object);
    AuthenticationTests.UnAuthorizedUserRedirects(controller, "Revoke");
}