3

I am trying to unit test some of my methods that rely on UserManager and RoleManager and am having some difficulty.

Should I mock the UserManager and RoleManager and then pass it to the AdminController? or should I first access the AccountController's default SignIn action and authenticate. I am unsure how to do both options or what the best way to approach this is.

When not authenticating / instantiating the managers I get NullReferenceExceptions on the UserManager

My test

    [Test]
    public void MaxRole_SuperAdmin()
    {
        var adminController = new AdminController();
        var maxRole = adminController.GetMaxRole(SuperAdminUserId);

        Assert.AreEqual(maxRole, "Super Admin");
    }

Controller and Method

[Authorize(Roles = "APGame Admin, APGame Investigator")]
[RequireHttps]
public class AdminController : Controller
{

    private ApplicationUserManager _userManager;

    public ApplicationUserManager UserManager
    {
        get { return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
        private set { _userManager = value; }
    }

    private ApplicationRoleManager roleManager;

    public ApplicationRoleManager RoleManager
    {
        get
        {
            return roleManager ?? HttpContext.GetOwinContext().Get<ApplicationRoleManager>();
        }
        private set { roleManager = value; }
    }

    public string GetMaxRole(string userId)
    {
        IEnumerable<string> userRoles = UserManager.GetRoles(userId);

        string role = null;

        if (userRoles.Contains("APGame Admin"))
        {
            if (userRoles.Contains("TeVelde Group") && userRoles.Contains("Genomics Group"))
                role = "Super Admin";

            else role = "Admin";
        }

        else if (userRoles.Contains("APGame Investigator"))
        {
            role = "Investigator";
        }

        else if (userRoles.Contains("APGame User"))
        {
            role = "User";
        }

        else
        {
            //TODO: Log no role, HIGH
        }

        return role;
    }
}
Eitan K
  • 763
  • 1
  • 10
  • 34
  • http://stackoverflow.com/questions/1452418/how-do-i-mock-the-httpcontext-in-asp-net-mvc-using-moq – Paul Abbott Mar 01 '16 at 20:55
  • @PaulAbbott what are the benefits of mocking the HttpContext over the UserManager and RoleManager – Eitan K Mar 02 '16 at 13:38
  • You're getting the user manager and role manager from the `HttpContext` in your code. If you mock the `HttpContext` to also return a mocked user and role manager, you don't need to modify your code at all. If you mock the user and role manager directly, you will need to change your code to inject them into the controller somehow instead of getting them from `HttpContext`. – Paul Abbott Mar 02 '16 at 16:17

2 Answers2

2

If you followed my blog-post, you should've got something like this for constructor of ApplicationUserManager:

public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store)
    {
     // configuration-blah-blah
    }
}

and your controller should have user manager object injected into constructor:

public class AdminController : Controller
{
    private readonly ApplicationUserManager userManager;
    public AdminController(ApplicationUserManager userManager)
    {
        this.userManager = userManager;
    }
}

Now for your test - you need a mocking framework. I few years back I used to put MOQ in every single test, now mocking framework of preference is NSubstitue because of more readable syntax. Current time I very rarely use mocking substitutes and prefer integration tests, even to dip into a DB, but this is not an objective for this question/discussion.

So for you test you need to create your System Under Test (SUT) - AdminController:

[Test]
public void MaxRole_SuperAdmin()
{
    var userStoreStub = NSubstitute.Substitute.For<IUserStore<ApplicationUser>>();
    var userManager = new ApplicationUserManager(userStoreStub);
    var sut = new AdminController(userManager);

    //TODO set up method substitutions on userStoreStub - see NSubstitute documentation

    var maxRole = sut.GetMaxRole(SuperAdminUserId);

    Assert.AreEqual(maxRole, "Super Admin");
}

But this is very clumsy test. You are trying to test stuff that is too deep down. I'd recommend you move GetMaxRole record off from controller into a separate class - a service class, or this can be a part of ApplicationUserManager or can be a QueryHandler if you prefer. Whatever you call it, it should not really be part of a controller. I think this method actually belongs to ApplicationUserManager. This way your testing layers is reduced by one and you only really need to create user manager class, not the controller.

Given that GetMaxRole() method is part of ApplicationUserManager your test will become smaller:

[Test]
public void MaxRole_SuperAdmin()
{
    var userStoreStub = NSubstitute.Substitute.For<IUserStore<ApplicationUser>>();
    var sut = new ApplicationUserManager(userStoreStub);

    //TODO set up method substitutions on userStoreStub - see NSubstitute documentation

    var maxRole = sut.GetMaxRole(SuperAdminUserId);

    Assert.AreEqual(maxRole, "Super Admin");
}

Reasons to move the tested method out from controller are as follows:

  • Over time I find that controllers are prone to be changed often with new dependencies added/removed/replaced. And if you have a few tests around controllers, you will have to change every test changed dependencies are used.
  • If you are to use GetMaxRole method some other place outside AdminController, you are in trouble. Controllers are not meant to share methods other than provide HTTP(S) endpoints.
  • Logic of your method look like a Domain Logic or Business Logic. This should be tested thoroughly. Controllers are not easy to test because they deal with HTTP requests and HttpContext which is not simple to test (though possible). Also it is best to avoid controller stuff mixed with Business Logic - helps you avoiding spaghetti code syndrome.

I can go on forever about this topic, but best read DI book by Mark Seeman and Unit Testing book by Roy Osherove.

trailmax
  • 31,605
  • 20
  • 126
  • 225
  • The issue I am having is that my Post action which calls GetMaxRole also calls the RoleManager, and my RoleManager is giving an exception because RoleManager.Roles does not contain anything. Additionally, when I try your implementation of adding GetMaxRole() to ApplictionUserManager and just testing ApplicationManager as opposed to the controller, I get the error Store does not implement IUserRoleStore – Eitan K Mar 08 '16 at 14:24
1

You should mock the UserManager and the RoleManager and pass them to the AdminController

andreasnico
  • 1,360
  • 12
  • 22
  • I was thinking that but I am having difficulty. Is there any resources you would recommend on how to mock/instantiate UserManager and Role Manager? – Eitan K Mar 02 '16 at 13:42
  • 1
    Your best bet is to start using dependency injection of some sort (there are plenty of well-documented libraries available through nuget). Having all your external dependencies injected into a class makes unit testing very, very easy. – Paul Abbott Mar 02 '16 at 16:23
  • That sounds great but I have no clue even where to start. I will do some research on dependency injection, thanks a lot!. If you have any good resources on this that would be greatly appreciated! – Eitan K Mar 02 '16 at 21:18
  • 1
    @EitanK you can start from my old blog-post about Identity and DI: http://tech.trailmax.info/2014/09/aspnet-identity-and-ioc-container-registration/ but also there are other resources: http://stackoverflow.com/questions/21927785/configure-unity-di-for-asp-net-identity https://www.talksharp.com/configuring-autofac-to-work-with-the-aspnet-identity-framework-in-mvc-5 – trailmax Mar 03 '16 at 01:09
  • 1
    @trailmax First of all I need to tell you how thorough and great your blog is, thanks a lot! I have implemented your implementation and made it so Unity loads RoleManager as well. However, I am still unsure as how to unit test and how this makes testing easier. I tried testing by Mocking the UserStore and RoleStore, passing them into ApplicationUserManager and ApplicationRoleManager and then calling my controller. The issue I am getting is that Store does not implement IUserRoleStore. Additionally, I see that ApplicationRoleManager.Roles throws a NotSupported exception – Eitan K Mar 03 '16 at 20:44