locked
ASP.NET MVC 5 with NUnit and Moq; how to mock RoleManager so it is possible to setup it's extension methods? RRS feed

  • Question

  • User1498918313 posted

    I have ASP.NET MVC 5 (.NET Framework) app with Entity Framework 6.2.
    I'm using NUnit and Moq frameworks for testing.

    I need to test if roleResult.Succeeded is true when RoleExists() extension(static) method returns false for "User" role.

    HomeController.cs :

    private ApplicationDbContext _db;
    private RoleManager<IdentityRole> _roleManager;
    private ApplicationUserManager _userManager;
    
    // Hard Entity Framework dependency (working application)
    public HomeController()
    {
    _db = new ApplicationDbContext();
    _roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(_db));
    _userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(_db));
    if (!_roleManager.RoleExists("User"))
    {
    IdentityResult roleResult = _roleManager.Create(new IdentityRole("User"));
    }
    }
    
    // Dependency injection only for testing
    public HomeController(ApplicationDbContext context, ApplicationUserManager userManager, RoleManager<IdentityRole> roleManager, string role) // DI for testing
    {
    _db = context;
    _roleManager = roleManager;
    _userManager = userManager;
    if (!_roleManager.RoleExists(role))
    {
    IdentityResult roleResult = _roleManager.Create(new IdentityRole(role));
    }
    }

    HomeTest.cs :

    [Test]
    public void HomeController_RoleDoesNotExist_AddRole()
    {
    var mockContext = new Mock<ApplicationDbContext>();
    var mockUserManager = new Mock<ApplicationUserManager>();
    var mockRoleManager = new Mock<RoleManager<IdentityRole>>();
    
    mockRoleManager.Setup(x => x.RoleExists("User")).Returns(false); // Moq says: nope :<
    
    var homeController = new HomeController(mockContext.Object, mockUserManager.Object, mockRoleManager.Object, "User");
    
    // Some Assert that I really don't know to deal with.
    }

    First: I don't know how implement testing code responsible for getting roleResult.Succeeded value.

    Second: Moq cannot setup static methods and I don't know how to deal with it in my situation. I'll probably need to replace private properties in my HomeController class with some new interfaces but then my Hard Entity Framework dependency will stop working because of for example: _db = new ApplicationDbContext() cannot be converted to IMyNewAppDbContext.

    Thanks for help.

    Sunday, December 30, 2018 5:05 PM

All replies

  • User1120430333 posted

    How does the HomeController have anything to do with role management? 

    Sunday, December 30, 2018 7:32 PM
  • User1498918313 posted

    For example I use view to choose role for current user and then send choosen role string to controller via form and there I set choosen role to current user via _userManager.AddToRole(userId, role);

    Sunday, December 30, 2018 9:07 PM
  • User1120430333 posted

    How does the HomeController have anything to do with role management? 

    IMO, you should be creating some kind of a model object that's using an Interface call it UserRole with an IUserRole or whatever you want to call it that has the user role logic you have sitting in the HomeController, and the HomeController calls the model object taking the code out of the controller. That way you can mockout UserRole based on the IUserRole.

    As far as the model object UserRole, it's fowling MVC principles of Model View Controller.

    https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/overview/understanding-models-views-and-controllers-cs

    <copied>

     An MVC model contains all of your application logic that is not contained in a view or a controller. The model should contain all of your application business logic, validation logic, and database access logic. For example, if you are using the Microsoft Entity Framework to access your database, then you would create your Entity Framework classes (your .edmx file) in the Models folder.

    A view should contain only logic related to generating the user interface. A controller should only contain the bare minimum of logic required to return the right view or redirect the user to another action (flow control). Everything else should be contained in the model.

    In general, you should strive for fat models and skinny controllers. Your controller methods should contain only a few lines of code. If a controller action gets too fat, then you should consider moving the logic out to a new class in the Models folder.

    <end>

    I should be able to mockout modelHelper.IsEndDateLessThanStartDate during a unit test.

    namespace ProgMgmntCore2UserIdentity.Models
    {
        public interface IModelHelper
        {
            bool IsEndDateLessThanStartDate(object obj, string type);
        }
    }
    ==============================================================
    
    namespace ProgMgmntCore2UserIdentity.Models
    {
        public class ModelHelper : IModelHelper
        {
            public bool IsEndDateLessThanStartDate(object obj, string type)
            {
                var value = false;
    
                if (type == "Project")
                {
                    var project = (ProjectViewModels.Project) obj;
    
                    if (project.EndDate < project.StartDate) value = true;
                }
                else
                {
                    var task = (TaskViewModels.TaskEdit) obj;
    
                    if (task.EndDate < task.StartDate) value = true;
                }
    
                return value;
            }
        }
    }
     [Authorize]
            [HttpPost]
            public ActionResult Create(ProjectViewModels.Project project, string submit)
            {
                if (submit == "Cancel") return RedirectToAction("Index");
    
                ValidateddlProjectTypes();
    
                project.ProjectType = (Request.Form["ddlProjectTypes"]);
    
                if (ModelState.IsValid && _modelHelper.IsEndDateLessThanStartDate(project, "Project"))
                    ModelState.AddModelError(string.Empty, "End Date cannot be less than Start Date.");
    
                if (!ModelState.IsValid) return View(_projectModel.PopulateSelectedList(project));
    
                _projectModel.Create(project, User.Identity.Name);
                return RedirectToAction("Index");
            }

    Monday, December 31, 2018 7:33 AM
  • User1498918313 posted

    Well I made some changes and create some files for logic separation.

    ContextProvider.cs:

    namespace ASP_Projekt.Models
    {
        public interface IContextProvider
        {
            ApplicationDbContext GetContext();
        }
    
        public class ContextProvider : IContextProvider
        {
            public ApplicationDbContext GetContext()
            {
                return new ApplicationDbContext();
            }
        }
    }

    AppRoleManager.cs:

    namespace ASP_Projekt.Models
    {
        public interface IAppRoleManager
        {
            bool RoleExists(string roleName);
            IdentityResult Create(IdentityRole role);
        }
    
        public class AppRoleManager : IAppRoleManager
        {
            private ApplicationDbContext _db;
            private RoleManager<IdentityRole> _roleManager;
    
            public AppRoleManager()
            {
                _db = new ContextProvider().GetContext();
                _roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(_db));
            }
    
            public AppRoleManager(ApplicationDbContext context, RoleManager<IdentityRole> roleManager)
            {
                _db = context;
                _roleManager = roleManager;
            }
    
            public bool RoleExists(string roleName)
            {
                return _roleManager.RoleExists(roleName);
            }
    
            public IdentityResult Create(IdentityRole role)
            {
                return _roleManager.Create(role);
            }
    
            public void CloseDb()
            {
                _db.Dispose();
            }
        }
    }

    RoleService.cs:

    namespace ASP_Projekt.Services
    {
        public class RoleService
        {
            private IAppRoleManager _roleManager;
            public RoleService(IAppRoleManager roleManager)
            {
                _roleManager = roleManager;
            }
    
            public void RoleInit(List<string> roles)
            {
                foreach (string role in roles)
                {
                    if (!_roleManager.RoleExists(role))
                    {
                        IdentityResult roleResult = _roleManager.Create(new IdentityRole(role));
                        if (roleResult.Errors.Any())
                        {
                            foreach (string error in roleResult.Errors)
                            {
                                throw new Exception(error);
                            }
                        }
                    }
                }
            }
        }
    }

    HomeTest.cs:

    var mockRoleManager  = new Mock<IAppRoleManager>();
    mockRoleManager.Setup(x => x.RoleExists("User")).Returns(false);
    mockRoleManager.Setup(x => x.Create(new IdentityRole() { Id = "id1", Name = "User"}));
    
    var roleService = new RoleService(mockRoleManager.Object);
    roleService.RoleInit(new List<string>()
    {
    	"User", "Admin"
    });


    Please, comment to this.
    In addition, I do not know how to do `Assert` for this test (info in the first post).

    Monday, December 31, 2018 5:41 PM
  • User475983607 posted

    I'm not sure why you're unit testing the ASP.NET Identity APIs.   Take a look at the ASP.NET Core Identity on GitHub for test examples. 

    https://github.com/aspnet/Identity

    I would mock the data stores that the APIs use not the APIs.  Here's a blog that illustrates the idea.

    https://alastairchristian.com/mocking-asp-net-identity-2-usermanager-methods-b740c703b580

    Monday, December 31, 2018 5:59 PM
  • User1120430333 posted

    The only thing that you could Assert on is that a method or methods were called that were mocked and that the test didn't blow up becuase the mocks were used. 

    https://jonathanroywelch.wordpress.com/2014/06/20/nunit-moq-asserting-a-method-is-called/

     

    Monday, December 31, 2018 8:47 PM
  • User1498918313 posted

    DA924I found that link content extremely easy....

    So instead of checking

    IdentityResult roleResult.Succeeded

    bool value I JUST need to verify if Create method was executed once?!
    So whatever It make sense or not (only for knowledge), how to check this result value?

    ______________
    Btw.

    To be honest. What about technical aspect of my new implementation. I was trying to minimize HomeController logic amount.

    Tuesday, January 1, 2019 10:25 AM
  • User1120430333 posted

    DA924I found that link content extremely easy....

    So instead of checking

    IdentityResult roleResult.Succeeded

    bool value I JUST need to verify if Create method was executed once?!
    So whatever It make sense or not (only for knowledge), how to check this result value?

    ______________
    Btw.

    To be honest. What about technical aspect of my new implementation. I was trying to minimize HomeController logic amount.

    Well, you do have some other issues in doing  a unit test on the functionality of the code you're trying to unit test,  becuase the test seems to be actually using a database and that makes it not a unit test, but rather, it's an integration test.

    https://www.artima.com/weblogs/viewpost.jsp?thread=126923

    You would need to find a way of mocking out the Entity Framework DBcontext. IMO, you would have to use ASP.NET Identity and IoC registration which there are articles out on Bing and Google. I can't say that it's worth doing all of that just to mock out the Dbcontext and do a unit test.

    Maybe, you should just do an integration test, no mocking of anything,  do it for real and just to test functionality for real. And just know that it passed the integration test and leave it at that.

    However, if you ever want to use DI in a ASP.NET MVC controller that is using ASP.NET Identity logic, then you will need to use ASP.NET Identity and IoC registration. 

    Tuesday, January 1, 2019 1:04 PM