Eitan K
Eitan K

Reputation: 837

Unit Test Relies on UserManager and RoleManager

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

Upvotes: 4

Views: 3864

Answers (2)

trailmax
trailmax

Reputation: 35106

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.

Upvotes: 2

andreasnico
andreasnico

Reputation: 1488

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

Upvotes: 1

Related Questions