Reputation: 837
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
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:
GetMaxRole
method some other place outside AdminController
, you are in trouble. Controllers are not meant to share methods other than provide HTTP(S) endpoints.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
Reputation: 1488
You should mock the UserManager and the RoleManager and pass them to the AdminController
Upvotes: 1