How to mock UserManager in .Net Core testing?

aspnet/Identity is opensource so what you can do is see how they mock it themselves.

Here's how they do it: MockHelpers.cs

TestUserManager

public static UserManager<TUser> TestUserManager<TUser>(IUserStore<TUser> store = null) where TUser : class
{
    store = store ?? new Mock<IUserStore<TUser>>().Object;
    var options = new Mock<IOptions<IdentityOptions>>();
    var idOptions = new IdentityOptions();
    idOptions.Lockout.AllowedForNewUsers = false;
    options.Setup(o => o.Value).Returns(idOptions);
    var userValidators = new List<IUserValidator<TUser>>();
    var validator = new Mock<IUserValidator<TUser>>();
    userValidators.Add(validator.Object);
    var pwdValidators = new List<PasswordValidator<TUser>>();
    pwdValidators.Add(new PasswordValidator<TUser>());
    var userManager = new UserManager<TUser>(store, options.Object, new PasswordHasher<TUser>(),
        userValidators, pwdValidators, new UpperInvariantLookupNormalizer(),
        new IdentityErrorDescriber(), null,
        new Mock<ILogger<UserManager<TUser>>>().Object);
    validator.Setup(v => v.ValidateAsync(userManager, It.IsAny<TUser>()))
        .Returns(Task.FromResult(IdentityResult.Success)).Verifiable();
    return userManager;
}

I know this is months old but I keep getting back to this thread. I will extend my own answer on this topic because just pointing to Haok's GitHub example is like saying: "Read a book" as it is huge. It does not pinpoint the issue and what you need to do. You need to isolate a Mock object, but not only that but also you need to 'Setup' the method for 'CreateAsync'. So let's put this in three parts:

  1. You need to MOCK if you are using MOQ or a similar framework to make a mocked up creation of the UserManager.
  2. You need to Setup the methods of UserManager you expect to get results back from.
  3. Optionally you would want to inject some generic list from a mocked Entity Framework Core 2.1 or similar so that you can actually see that a list of IDentity Users actually increases or decreases. Not just that UserManager succeeded and nothing else

So say I have a helper method for returning a Mocked UserManager. Which is just slightly altered from the Haok code:

public static Mock<UserManager<TUser>> MockUserManager<TUser>(List<TUser> ls) where TUser : class
{
    var store = new Mock<IUserStore<TUser>>();
    var mgr = new Mock<UserManager<TUser>>(store.Object, null, null, null, null, null, null, null, null);
    mgr.Object.UserValidators.Add(new UserValidator<TUser>());
    mgr.Object.PasswordValidators.Add(new PasswordValidator<TUser>());

    mgr.Setup(x => x.DeleteAsync(It.IsAny<TUser>())).ReturnsAsync(IdentityResult.Success);
    mgr.Setup(x => x.CreateAsync(It.IsAny<TUser>(), It.IsAny<string>())).ReturnsAsync(IdentityResult.Success).Callback<TUser, string>((x, y) => ls.Add(x));
    mgr.Setup(x => x.UpdateAsync(It.IsAny<TUser>())).ReturnsAsync(IdentityResult.Success);

    return mgr;
}

What is key to this is I am injecting a generic 'TUser' that is what I will be testing as well injecting a list of this. Similar to my example of:

 private List<ApplicationUser> _users = new List<ApplicationUser>
 {
      new ApplicationUser("User1", "[email protected]") { Id = 1 },
      new ApplicationUser("User2", "[email protected]") { Id = 2 }
 };
    
 ...

 private _userManager = MockUserManager<ApplicationUser>(_users).Object; 

Then finally I am testing a pattern with a repository similar to this implementation I want to test:

 public async Task<int> CreateUser(ApplicationUser user, string password) => (await _userManager.CreateAsync(user, password)).Succeeded ? user.Id : -1;

I test it like this:

 [Fact]
 public async Task CreateAUser()
 {
      var newUser = new ApplicationUser("NewUser", "[email protected]");
      var password = "P@ssw0rd!";

      var result = await CreateUser(newUser, password);

      Assert.Equal(3, _users.Count);
  }

The key to what I did is that not only did I 'Setup' the CreateAsync but I provided a callback so I can actually see my list I inject get incremented. Hope this helps someone.