Search code examples
c#asp.net-mvcunit-testingidentity

unit test method that uses identity in regular mvc controller (not web api controller)


I have a method in a regular mvc controller that checks the user's identity to see which page the user should be directed to. However, building a unit test against this method raises an "object reference not set" error as User is always null when the method is run as part of a test.

public ActionResult Index()
        {
            if (User.Identity.IsAuthenticated)
            {
                if (User.IsInRole("Administrator"))
                {
                    return RedirectToAction("Console");
                }

                ViewBag.StatusMessage = "You are logged in but do not have sufficient permissions.";
            }
            else
            {
                ViewBag.StatusMessage = "Please log in";
            }

            return View();
        }

I have tried various solutions offered at Writing Unit Test for methods that use User.Identity.Name in ASP.NET Web API. These generally use something like the following inside the body of the unit test:

var identity = new GenericIdentity("UserName");
Thread.CurrentPrincipal = new GenericPrincipal(identity, null);
var controller = new FooController(); 

This approach works fine if the Controller is an ApiController - i.e. a controller for a WebApi project but it does not seem to work for a regular web mvc controller. The User object remains null. How to solve?


Solution

  • This link which you have posted related to WebAPI, and quite understandably it is very easy Unit Test the ApiController and access the User object.

    This is some what different to ASP.NET MVC as you have seen it is not straightforward. If we go down the path where using manual/handwritten mocks, we probably find our selves mocking the entire universe as oppose to write a valuable test.

    I include the both approaches so you can get the idea. I 'm also use the same example from the link have posted in the question.

    Using manual hand written mocks

    //System Under Test - i.e to test User
    public class SutController : Controller
    {
        public string Get() {
            return User.Identity.Name;
        }
    }
    
    public class TestableControllerContext : ControllerContext {
        public TestableHttpContext TestableHttpContext { get; set; }
    }
    
    public class TestableHttpContext : HttpContextBase {
        public override IPrincipal User { get; set; }
    }
    
    [TestMethod]
    public void IndexNoneMoq()
    {
        var identity = new GenericIdentity("tugberk");
        var controller = new SutController();
    
        var controllerContext = new TestableControllerContext();
        var principal = new GenericPrincipal(identity, null);
        var testableHttpContext = new TestableHttpContext
        {
            User = principal
        };
    
        controllerContext.HttpContext = testableHttpContext;
        controller.ControllerContext = controllerContext;
    
        Assert.AreEqual(controller.Get(), identity.Name);
    }
    

    Using Mocking/Isolation framework i.e Moq

    [TestMethod]
    public void IndexMoq()
    {
        var identity = new GenericIdentity("tugberk");          
        var controller = new SutController();
    
        var controllerContext = new Mock<ControllerContext>();
        var principal = new Mock<IPrincipal>();
        principal.Setup(p => p.IsInRole("Administrator")).Returns(true);
        principal.SetupGet(x => x.Identity.Name).Returns("tugberk");
        controllerContext.SetupGet(x => x.HttpContext.User).Returns(principal.Object);
        controller.ControllerContext = controllerContext.Object;
    
        Assert.AreEqual(controller.Get(), identity.Name);
    }
    

    Note that I think the next version of ASP.NET uses the same underlying components for both WebAPI and MVC (AFAIK), so this could be a one way Unit testing target controller which uses the User instance.