Search code examples
unit-testingasp.net-coreasp.net-identitymoqxunit

UserManager not creating users in Unit Tests


I'm using xUnit & Moq and what I'm trying to achieve is typically create Identity User so it's stored in Entity Framework In-Memory DB.

I have established Seed Data functionality - it gets triggered when my ASP.NET Core host app starts and works flawlessly - so no problem.

But the issue occurs when I use a mocked UserManager. No exceptions are thrown, users are just not being saved.

Verified while debugging tests, DbContext returns 0 users, also UserManager.FindByNameAsync yields null.

I wonder what is the cause. Could it be due to the way I assemble UserManager in constructor of SeedDataTest class?

public class SeedDataTest
{
    private AppDbContext dbContext;
    private UserManager<ApplicationUser> userManager;
    private ITenantService tenantService;

    public SeedDataTest()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(databaseName: "in_memory_db")
            .Options;

        dbContext = new AppDbContext(options);

        var userStore = new UserStore<ApplicationUser>(dbContext);

        userManager = new Mock<UserManager<ApplicationUser>>(
            userStore,
            new Mock<IOptions<IdentityOptions>>().Object,
            new Mock<IPasswordHasher<ApplicationUser>>().Object,
            new IUserValidator<ApplicationUser>[0],
            new IPasswordValidator<ApplicationUser>[0],
            new Mock<ILookupNormalizer>().Object,
            new Mock<IdentityErrorDescriber>().Object,
            new Mock<IServiceProvider>().Object,
            new Mock<ILogger<UserManager<ApplicationUser>>>().Object)
            .Object;

        tenantService = new Mock<TenantService>(dbContext).Object;
    }

    [Fact]
    public void Test1()
    {
        new TenantsCreator(dbContext).Create();
        new UserCreator(dbContext, tenantService, userManager).Create(); // stuck here
        new MembershipCreator(dbContext, userManager).Create();

        // unfinished
    }
}

And here is the code from UserCreator

public class UserCreator
{
    private AppDbContext _context;
    private ITenantService _tenantService;
    private UserManager<ApplicationUser> _userManager;

    public UserCreator(
        AppDbContext context,
        ITenantService tenantService,
        UserManager<ApplicationUser> userManager
        )
    {
        _context = context;
        _tenantService = tenantService;
        _userManager = userManager;
    }

    public void Create()
    {
        Task.Run(async () => await CreateUsers()).ConfigureAwait(false).GetAwaiter().GetResult();
    }

    private async Task CreateUsers()
    {
        ApplicationUser hostAdminUser = _context.Users.FirstOrDefault(x => x.UserName.Equals(SetupConsts.Users.AdminJoe.UserName));

        if (hostAdminUser == null)
        {
            hostAdminUser = new ApplicationUser()
            {
                FirstName = SetupConsts.Users.AdminJoe.FirstName,
                LastName = SetupConsts.Users.AdminJoe.LastName,
                UserName = SetupConsts.Users.AdminJoe.UserName,
                Email = SetupConsts.Users.AdminJoe.Email,
                EmailConfirmed = true,
                PasswordHash = new PasswordHasher<ApplicationUser>().HashPassword(hostAdminUser, SetupConsts.Users.Passwords.Default)
            };

            await _userManager.CreateAsync(hostAdminUser);
        }

        ApplicationUser secondaryUser = _context.Users.FirstOrDefault(x => x.UserName.Equals(SetupConsts.Users.JohnRoe.UserName));

        if (secondaryUser == null)
        {
            secondaryUser = new ApplicationUser()
            {
                FirstName = SetupConsts.Users.JohnRoe.FirstName,
                LastName = SetupConsts.Users.JohnRoe.LastName,
                UserName = SetupConsts.Users.JohnRoe.UserName,
                Email = SetupConsts.Users.JohnRoe.Email,
                EmailConfirmed = true,
                PasswordHash = new PasswordHasher<ApplicationUser>().HashPassword(secondaryUser, SetupConsts.Users.Passwords.Default)
            };

            await _userManager.CreateAsync(secondaryUser);
        }
    }
}

Solution

  • we use mocking frameworks to allow us to build mocked (substituted) dependencies as well as specify what is returned from classes/interfces used within our SUT.

    A good use case for this is a database itself. We always want to know the exact state of objects whilst we are testing them and the only way we do that is by expressing the content of them ourselves.

    That is where Moq comes in. One of its features is to allow us to state the result of method calls.

    I believe you are seeing 0 results returned because you are using a moq implementation of a class (which doesnt actually call the implemented class). In order to get a result, you will need to do some setup:

    mockedClass.Setup(x => x.GetUserDetails(It.IsAny<int>())).Returns(new UserDetails());
    

    Either do it this way, or ensure you are passing a concrete implementation of the UserManager class rather than the mocked version:

     userManager = new UserManager<ApplicationUser>
    

    Hope that helps