Unit testing repositories in ASP.NET Core with xUnit and Moq
Image from Pixabay

Unit testing repositories in ASP.NET Core with xUnit and Moq

Mocking UserManager RoleManager and DbContext in unit tests in .NET Core

Unit testing is an important part for maintaining code quality and what is the most important thing, avoiding bugs in production. This is one of the reasons why unit tests and high code coverage is desired as a part of the project and as a part of the build process since you can detect potential bugs and issues during the build process when unit tests are executed. This means that your code does not reach production environment and deployment stage unless all unit tests are passed.

However some parts and layers of our applications are more difficult to unit test. This often leaves them untested or left for testing not in unit tests, but rather in some other form of testing like integration testing. One of these examples is testing of the data repository layer of the application. I saw this part in projects quite often covered with integration tests involving real persistence storage specifically used for integration tests only. Although integration tests are welcome, this is something that should be covered with unit tests in the first place.

Unit testing using Microsoft.EntityFrameworkCore.InMemory

One of the ways to test data repository layer of the application is to use Microsoft.EntityFrameworkCore.InMemory which is Microsoft's implementation of in-memory storage. In my opinion, this is also sort of integration test since you are just replacing the database with memory storage. You do not really mock the behavior of the persistence framework.

One of the problems that I noticed when testing the repository using InMemory package is that you are free to insert two entities of the same type with the same ID primary key. In the relational database in production this would raise an exception, but it does not happen in the InMemory storage. This pushed me to investigate and see how to actually mock the DbContext and setup the behavior like throwing exception when an entity with specific ID already exists.

More about testing with InMemory you can check at Testing with InMemory article which is part of the Microsoft .NET Core documentation.

Mocking the DbContext for the repository

To test your data repository you first need to mock the underlying EntityFrameworkCore layer. This means that you basically need to mock EntityFrameworkCore classes, particularly the your specific entity context which is derived from DbContext and DbSet as a part of the DbContext.

As a sample project for unit tests I am going to use the public repository from my GitHub account https://github.com/dejanstojanovic/WebShop. Here is an example of DbContext and DbSet mock to test ProductsRepository

        [Fact]
        public void GetByIdAsync_Returns_Product()
        {
            //Setup DbContext and DbSet mock
            var dbContextMock = new Mock<ProductsDbContext>();
            var dbSetMock = new Mock<DbSet<Product>>();
            dbSetMock.Setup(s => s.FindAsync(It.IsAny<Guid>())).Returns(Task.FromResult(new Product()));
            dbContextMock.Setup(s => s.Set<Product>()).Returns(dbSetMock.Object);

            //Execute method of SUT (ProductsRepository)
            var productRepository = new ProductsRepository(dbContextMock.Object);
            var product = productRepository.GetByIdAsync(Guid.NewGuid()).Result;

            //Assert
            Assert.NotNull(product);
            Assert.IsAssignableFrom<Product>(product);
        }
    
Note

In order to be able to Mock your DbContext derived class for your specific entity, your class (in this case ProductsDbContext) needs to have parameterless constructor. If not, Mock initialization will throw an exception.

Mocking DbConext and DbSet wasn't that hard but there are other commonly used libraries that may make unite test writing a bit difficult, but nevertheless they should be also covered by the unit tests. ASP.NET Core Identity Framework is one of these libraries which mocking can be not so straight forward.

Mocking ASP.NET Core Identity - UserManager & RoleManager

If you are working on the authorization and authentication part, there is a big change that you will involve ASP.NET Core Identity especially if you are starting a new project from the scratch. Unlike working directly with EntityFramework Core to access data, Identity Framework abstracts working with users and roles through UserManager and RoleManager. It also allows you to access DbContext, but you should do it only if it something too custom that UserManager or RoleManager do not expose which is quite rare.

Because of these two main classes for managing users and roles, mocking of data access classes is a bit different than with only mocking the DbConext. Mocking of DbConext still stays in place as you would want to have id in your repository just in case you have something too custom as I mentioned.

First thing when you notice when you go to UserManager constructor is that it has a lot of parameters and to mock it you need to mock all of them. This is one of the reasons mocking of UserManager is not so straight forward, but it is doable. It's just you need to mock all constructor parameters.

        Mock<UserManager<TIDentityUser>> GetUserManagerMock<TIDentityUser>() where TIDentityUser : IdentityUser
        {
            return new Mock<UserManager<TIDentityUser>>(
                    new Mock<IUserStore<TIDentityUser>>().Object,
                    new Mock<IOptions<IdentityOptions>>().Object,
                    new Mock<IPasswordHasher<TIDentityUser>>().Object,
                    new IUserValidator<TIDentityUser>[0],
                    new IPasswordValidator<TIDentityUser>[0],
                    new Mock<ILookupNormalizer>().Object,
                    new Mock<IdentityErrorDescriber>().Object,
                    new Mock<IServiceProvider>().Object,
                    new Mock<ILogger<UserManager<TIDentityUser>>>().Object);
        }
    

Same case goes for RoleManager, you need to mock all constructor parameters. 

        Mock<RoleManager<TIdentityRole>> GetRoleManagerMock<TIdentityRole>() where TIdentityRole : IdentityRole
        {
            return new Mock<RoleManager<TIdentityRole>>(
                    new Mock<IRoleStore<TIdentityRole>>().Object,
                    new IRoleValidator<TIdentityRole>[0],
                    new Mock<ILookupNormalizer>().Object,
                    new Mock<IdentityErrorDescriber>().Object,
                    new Mock<ILogger<RoleManager<TIdentityRole>>>().Object);
        }
    

Since this mock is not only one simple line, it is useful to have it as separate methods, so you can require UserManager or RoleManager mock as you need in your tests. To have this tested I wrote a unit test for user accounts repository from https://github.com/dejanstojanovic/WebShop/blob/master/src/WebShop.Users.Data/Repositories/ApplicationUsersRepository.cs to test user retrieve method.

        [Fact]
        public async Task GetUser_ReturnsUser()
        {
            var dbSetMock = new Mock<DbSet<ApplicationUser>>();
            var dbContextMock = new Mock<ApplicationDbContext>();
            dbContextMock.Setup(s => s.Set<ApplicationUser>()).Returns(dbSetMock.Object);

            var userManagerMock = GetUserManagerMock<ApplicationUser>();
            userManagerMock.Setup(u => u.FindByIdAsync(It.IsAny<String>())).Returns(Task.FromResult(new ApplicationUser()));

            var roleManagerMock = GetRoleManagerMock<IdentityRole>().Object;

            ApplicationUsersRepository applicationUsersRepository = new ApplicationUsersRepository(
                dbContextMock.Object,
                userManagerMock.Object,
                roleManagerMock
                );
            var user = await applicationUsersRepository.GetUser(Guid.NewGuid());
            Assert.NotNull(user);
            Assert.IsAssignableFrom<ApplicationUser>(user);
        }
    

Now as you saw from this article, mocking EntityFramework and Identity is not so straight forward as you would go with classes that implement specific interface, but it is doable and it is important to have your repositories covered with unit tests to avoid bug in production.

If you want to know more about dry code analysis and your code quality metrics, you can check Publishing .NET Core code analysis to SonarCloud from Azure build pipeline on how to automate you build process and code analysis.

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article