1

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

Shahar Shokrani
  • 7,598
  • 9
  • 48
  • 91
morgh
  • 59
  • 6
  • Just wondering what the benefit is of unit testing the Identity framework? I'm pretty sure that they have tested this behavior for you. Unless you are implementing your own password hasher of course. – Dennis VW Apr 19 '20 at 12:06
  • Autofixture is good solution if you keen to mock. – maxspan Apr 19 '20 at 12:21
  • @Dennis1679 - It's part of an api request which allow to register a user. So it should be tested as part of it in order to make sure correct response is coming back – morgh Apr 19 '20 at 15:51
  • @maxspan - any link example I could follow? – morgh Apr 19 '20 at 18:03
  • https://github.com/AutoFixture/AutoFixture – maxspan Apr 20 '20 at 00:16

1 Answers1

1

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.

Shahar Shokrani
  • 7,598
  • 9
  • 48
  • 91
  • Ah yes, you are correct. I am passing the registrationModel with username, email, password etc. but when I map that model to an entity the 'user' entity passwordHash is null (the reason for that is because I don't want to populate PasswordHash prop with an exposed password) and I would expect createAsync to create a hashed password based on the one from the registrationModel. But I am unsure what would be the best way to achieve that from testing perspective, any thoughts? – morgh Apr 19 '20 at 22:02
  • The answer depend on what is your goal, what are you trying to test? – Shahar Shokrani Apr 20 '20 at 06:25
  • I suggest you update your question properly or raise another one so I'll be able to help – Shahar Shokrani Apr 20 '20 at 08:46