In this article, we will learn how to perform Unit Testing with UserManager and RoleManager in ASP.NET Core Identity. Unit testing is an essential practice in software development that helps ensure the correctness and reliability of code. In the context of ASP.NET Core Identity, unit testing becomes even more critical, as it involves sensitive user-related operations such as registration and authentication.
We use the UserManager
class to perform user-related operations, such as user registration and authentication. Moreover, we use the RoleManager
class to manage and access user roles in our application. During unit testing, we need a way to mock those objects, to test the functionality of our controllers in isolation.
Here, we will test the user registration process that makes use of both objects. The code is based on the article on user registration that is part of the ASP.NET Core Identity series.
By the end of this article, you will have a comprehensive understanding of how to unit test the user registration process in ASP.NET Core Identity and improve the quality and reliability of your code.
Let’s start.
The User Registration Process
During registration, the user has to enter his details: first and last name, username, and password. Additionally, the user has to select the appropriate role (Visitor or Administrator).
The registration process is handled by the AccountController
class:
public class AccountController : Controller { private readonly IMapper _mapper; private readonly UserManager<User> _userManager; private readonly RoleManager<IdentityRole> _roleManager; public AccountController(IMapper mapper, UserManager<User> userManager, RoleManager<IdentityRole> roleManager) { _mapper = mapper; _userManager = userManager; _roleManager = roleManager; } //other methods omitted }
The Register()
method in the Account controller handles the registration POST request and uses UserManager
and RoleManager
to create a new user and assign them a role:
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Register(UserRegistrationModel userModel) { ViewData["roles"] = _roleManager.Roles.ToList(); if (!ModelState.IsValid) { return View(userModel); } var user = _mapper.Map<User>(userModel); var result = await _userManager.CreateAsync(user, userModel.Password); if(!result.Succeeded) { foreach (var error in result.Errors) { ModelState.TryAddModelError(error.Code, error.Description); } return View(userModel); } await _userManager.AddToRoleAsync(user, userModel.Role); return RedirectToAction(nameof(HomeController.Index), "Home"); }
Let’s see how to define those roles in the application.
Initially, let’s create a new class (RoleConfiguration
) that implements the IEntityTypeConfiguration<IdentityRole>
interface:
public class RoleConfiguration : IEntityTypeConfiguration<IdentityRole> { public void Configure(EntityTypeBuilder<IdentityRole> builder) { builder.HasData( new IdentityRole { Name = "Visitor", NormalizedName = "VISITOR" }, new IdentityRole { Name = "Administrator", NormalizedName = "ADMINISTRATOR" }); } }
RoleConfiguration
creates two new IdentityRole
objects, named Visitor
and Administrator
respectively.
Next, let’s override the OnModelCreating()
method in the database context file (ApplicationContext.cs
), to apply the new configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new RoleConfiguration()); }
Now, the new roles are available to the Account controller and are displayed with a drop-down box in the respective view. Next, let’s proceed with unit testing of this controller.
Unit Testing With UserManager and RoleManager
In order to unit test the registration controller in isolation, we will need to mock the UserManager
and RoleManager
objects. Mocking helps us simulate the external dependencies of the class under test. We will use Moq, a mock object framework for .NET that helps us create mock objects for unit testing.
First of all, let’s create a new xUnit test project and let’s add support for Moq. We will begin by testing a successful registration to ensure that the controller creates a new user and assigns them a role correctly.
Successful Registration Scenario
First, let’s define the user registration model and the user object that would be created by the object mapper:
var userRegistrationModel = new UserRegistrationModel() { FirstName = "Test1", LastName = "Test2", Email = "Test3", Password = "Test4", ConfirmPassword = "Test4" }; var user = new User() { UserName = "Test3", FirstName = "Test1", LastName = "Test2", Email = "Test3" };
Next, let’s mock the UserManager
object:
var userManagerMock = new Mock<UserManager<User>>( new Mock<IUserStore<User>>().Object, new Mock<IOptions<IdentityOptions>>().Object, new Mock<IPasswordHasher<User>>().Object, new IUserValidator<User>[0], new IPasswordValidator<User>[0], new Mock<ILookupNormalizer>().Object, new Mock<IdentityErrorDescriber>().Object, new Mock<IServiceProvider>().Object, new Mock<ILogger<UserManager<User>>>().Object); userManagerMock .Setup(userManager => userManager.CreateAsync(It.IsAny<User>(), It.IsAny<string>())) .Returns(Task.FromResult(IdentityResult.Success)); userManagerMock .Setup(userManager => userManager.AddToRoleAsync(It.IsAny<User>(), It.IsAny<string>()));
For the UserManager
mock object, we are mocking the two methods (CreateAsync()
and AddToRoleAsync()
).
Now, let’s mock the RoleManager
object:
var list = new List<IdentityRole>() { new IdentityRole("Administrator"), new IdentityRole("Visitor") } .AsQueryable(); var roleManagerMock = new Mock<RoleManager<IdentityRole>>( new Mock<IRoleStore<IdentityRole>>().Object, new IRoleValidator<IdentityRole>[0], new Mock<ILookupNormalizer>().Object, new Mock<IdentityErrorDescriber>().Object, new Mock<ILogger<RoleManager<IdentityRole>>>().Object); roleManagerMock .Setup(r => r.Roles).Returns(list);
The RoleManager
mock object will return a list of IdentityRole
objects when we access the Roles
property.
Finally, let’s also mock the Mapper
object and use it, along with the other two mock objects, to instantiate the AccountController
object:
var mapperMock = new Mock<IMapper>(); mapperMock.Setup(m => m.Map<User>(userRegistrationModel)).Returns(user); var controller = new AccountController(mapperMock.Object, userManagerMock.Object, roleManagerMock.Object); var result = (RedirectToActionResult) await controller.Register(userRegistrationModel); Assert.Equal("Index", result.ActionName);
Failed Registration Scenario
We will also test a scenario where the registration fails due to an existing username:
var userManagerMock = new Mock<UserManager<User>>( new Mock<IUserStore<User>>().Object, new Mock<IOptions<IdentityOptions>>().Object, new Mock<IPasswordHasher<User>>().Object, new IUserValidator<User>[0], new IPasswordValidator<User>[0], new Mock<ILookupNormalizer>().Object, new Mock<IdentityErrorDescriber>().Object, new Mock<IServiceProvider>().Object, new Mock<ILogger<UserManager<User>>>().Object); var identityErrors = new IdentityError[] { new IdentityError() { Code = "Username already exists", Description = "Username already exists" } }; userManagerMock .Setup(userManager => userManager.CreateAsync(It.IsAny<User>(), It.IsAny<string>())) .Returns(Task.FromResult(IdentityResult.Failed(identityErrors))); userManagerMock .Setup(userManager => userManager.AddToRoleAsync(It.IsAny<User>(), It.IsAny<string>()));
Here, we set up the CreateAsync()
method to return an IdentityError
object, indicating that the provided username already exists in the system. As a result, the original registration page loads again and displays the error message:
var controller = new AccountController(mapperMock.Object, userManagerMock.Object, roleManagerMock.Object); var result = (ViewResult)await controller.Register(userRegistrationModel); Assert.Null(result.ViewName);
Conclusion
In this article, we have learned how to perform Unit Testing with UserManager and RoleManager in ASP.NET Core Identity. By using mock objects, we were able to isolate the behavior of the controller and test it in a controlled environment. As a result, we can be more confident in the correctness of our code and catch errors early on in the development process.