Search code examples
c#unit-testing.net-coreasp.net-identitymoq

Testing usermanager creates a user but password hash property is null


I am working with Microsoft.AspNetCore.Identity.UserManager and I'm trying to mock the creation of a new user. In fact, it does create a new user with username, email etc. but the password hash property is still null.

This is how I set up mock usermanager with some additional setup:

        var store = new Mock<IUserPasswordStore<User>>();

        var validator = new UserValidator<User>();
        var passValidator = new PasswordValidator<User>();

        var mgr = new Mock<UserManager<User>>(store.Object, null, null, null, null, null, null, null, null);
        mgr.Object.UserValidators.Add(validator);
        mgr.Object.PasswordValidators.Add(passValidator);
        mgr.Object.PasswordHasher = new PasswordHasher<User>();
        mgr.Object.Options = AuthenticationRules.DefaultAuthenticationRules();

        List<User> users= new List<User>();

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

The 'DefaultAuthenticationRules' returns this:

    public static IdentityOptions DefaultAuthenticationRules(IdentityOptions identityOptions = null)
    {
        if(identityOptions == null)
            identityOptions = new IdentityOptions();

        identityOptions.User.RequireUniqueEmail = true;
        identityOptions.Password.RequireNonAlphanumeric = false;
        identityOptions.Password.RequiredUniqueChars = 0;

        return identityOptions;
    }

I am then passing the mgr.Object to a method that handles the creation of the new user where 'Object' is _userManager

        var creationResult = await _userManager.CreateAsync(_user, _registrationModel.Password);

        if (!creationResult.Succeeded)
            return false;

        return true;

_registrationModel.Password IS populated.

So now when the setup adding a new user in the callback to the list of users the user is populated without password hash. I am not exactly sure what I'm missing here. I'm missing something in mgr.Setup?

Thanks in advance


Solution

  • The callback function of yours Callback<User, string>((x, y) => users.Add(x)); will be executed after the completion of the test, it will accept as a parameter the same parameter value that you pass to the mocked method, in your case the password is probably an empty string or null.

    _registrationModel.Password IS populated.

    Ok, but the inner code that generate some password does not have any influence on the (x,y) => {..} params.

    See this code (copied from this great answer):

    public interface IFoo
    {
        int Bar(bool b);
    }
    
    var mock = new Mock<IFoo>();
    
    mock.Setup(mc => mc.Bar(It.IsAny<bool>()))
        .Callback<bool>(b => Console.WriteLine("Bar called with: " + b))
        .Returns(99999);
    
    var ret1 = mock.Object.Bar(true);
    Console.WriteLine("Result ret1: " + ret1);
    var ret2 = mock.Object.Bar(false);
    Console.WriteLine("Result ret2: " + ret2);
    
    //OUTPUT:
    //Result ret1: true.
    //Result ret1: false.
    

    In the example, regardless of what is happening inside the Bar method, the output will be depend only upon the calling param value of the mocked function.