Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking a SignInManager

New to unit testing with Moq and xUnit. I am trying to mock a SignInManager that is used in a controller constructor to build a unit test. The documentation that I can find for the SignInManager constructor says it accepts a UserManager and AuthenticationManager object: https://msdn.microsoft.com/en-us/library/mt173769(v=vs.108).aspx#M:Microsoft.AspNet.Identity.Owin.SignInManager`2.

When I try to mock the controller, I get an error saying it was unable to instantiate a proxy of the SignInManager and AuthenticationManager classes.

Error:

"Message: Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: Microsoft.AspNetCore.Identity.SignInManager1[[Models.AppUser, , Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]. Could not find a constructor that would match given arguments: Castle.Proxies.UserManager`1Proxy Castle.Proxies.AuthenticationManagerProxy"

The unit test:

public void Can_Send_Password_Reset_Email()
{
    //Arrange
    //create mock services
    Mock<IEmailService> mockEmailService = new Mock<IEmailService>();
    Mock<ILessonRepository> mockRepo = new Mock<ILessonRepository>();
    Mock<UserManager<AppUser>> mockUsrMgr = GetMockUserManager();
    var mockSignInMgr = GetMockSignInManager();
    Mock<UserValidator<AppUser>> mockUsrVal = new Mock<UserValidator<AppUser>>();
    Mock<PasswordValidator<AppUser>> mockPwdVal = new Mock<PasswordValidator<AppUser>>();
    Mock<PasswordHasher<AppUser>> mockPwdHshr = new Mock<PasswordHasher<AppUser>>();
    Mock<ForgotPasswordModel> model = new Mock<ForgotPasswordModel>();
    model.Object.Email = "[email protected]";

    var user = new AppUser();
    var token = mockUsrMgr.Object.GeneratePasswordResetTokenAsync(user).Result;

    //create mock temporary data, needed for controller message
    Mock<ITempDataDictionary> tempData = new Mock<ITempDataDictionary>();
    //create the controller
    //ERROR ON THIS LINE
    AccountController controller = new AccountController(mockUsrMgr.Object, mockSignInMgr.Object, mockUsrVal.Object, mockPwdVal.Object, mockPwdHshr.Object, mockEmailService.Object)
    {
        TempData = tempData.Object
    };

    //Act
    //the controller should call the email action method
    controller.PasswordResetEmail(model.Object);

    //Assert
    //verify that the email service method was called one time
    mockEmailService.Verify(m => m.PasswordResetMessage(user, "Test Email"), Times.Once());
}

The SignInManager mocking function:

//create a mock SignInManager class
private Mock<SignInManager<AppUser>> GetMockSignInManager()
{
    var mockUsrMgr = GetMockUserManager();
    var mockAuthMgr = new Mock<AuthenticationManager>();
    return new Mock<SignInManager<AppUser>>(mockUsrMgr.Object, mockAuthMgr.Object);
}

The GetMockUserManager() works fine in other unit tests and doesn't appear to be the issue.

like image 254
coolhand Avatar asked Jan 10 '18 14:01

coolhand


4 Answers

This is how I do it, I hope this helps you.

public class FakeSignInManager : SignInManager<ApplicationUser>
{
    public FakeSignInManager()
            : base(new FakeUserManager(),
                 new Mock<IHttpContextAccessor>().Object,
                 new Mock<IUserClaimsPrincipalFactory<ApplicationUser>>().Object,
                 new Mock<IOptions<IdentityOptions>>().Object,
                 new Mock<ILogger<SignInManager<ApplicationUser>>>().Object,
                 new Mock<IAuthenticationSchemeProvider>().Object)
        { }        
}



public class FakeUserManager : UserManager<ApplicationUser>
    {
        public FakeUserManager()
            : base(new Mock<IUserStore<ApplicationUser>>().Object,
              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)
        { }

        public override Task<IdentityResult> CreateAsync(ApplicationUser user, string password)
        {
            return Task.FromResult(IdentityResult.Success);
        }

        public override Task<IdentityResult> AddToRoleAsync(ApplicationUser user, string role)
        {
            return Task.FromResult(IdentityResult.Success);
        }

        public override Task<string> GenerateEmailConfirmationTokenAsync(ApplicationUser user)
        {
            return Task.FromResult(Guid.NewGuid().ToString());
        }

    }
like image 131
Denny Puig Avatar answered Nov 19 '22 19:11

Denny Puig


For ASP.NET Core 5, this setup can be used:

var userManagerMock = new Mock<UserManager<User>>(
    /* IUserStore<TUser> store */Mock.Of<IUserStore<User>>(),
    /* IOptions<IdentityOptions> optionsAccessor */null,
    /* IPasswordHasher<TUser> passwordHasher */null,
    /* IEnumerable<IUserValidator<TUser>> userValidators */null,
    /* IEnumerable<IPasswordValidator<TUser>> passwordValidators */null,
    /* ILookupNormalizer keyNormalizer */null,
    /* IdentityErrorDescriber errors */null,
    /* IServiceProvider services */null,
    /* ILogger<UserManager<TUser>> logger */null);

var signInManagerMock = new Mock<SignInManager<User>>(
    userManagerMock.Object,
    /* IHttpContextAccessor contextAccessor */Mock.Of<IHttpContextAccessor>(),
    /* IUserClaimsPrincipalFactory<TUser> claimsFactory */Mock.Of<IUserClaimsPrincipalFactory<User>>(),
    /* IOptions<IdentityOptions> optionsAccessor */null,
    /* ILogger<SignInManager<TUser>> logger */null,
    /* IAuthenticationSchemeProvider schemes */null,
    /* IUserConfirmation<TUser> confirmation */null);
like image 25
huysentruitw Avatar answered Nov 19 '22 19:11

huysentruitw


the SignInManager Has no Constructor with no argument so you have to pass the arguments in the new Mock<SignInManager>(**Params here**)

Some of these paramaters cannot not be null so they have to be mocked too like :

  1. UserManager<TUser> ==> the First Argument.
  2. IHttpContextAccessor ==> the second argument.

The probleme here is even UserManager have not a parameter less constructor so you have to mock with its parameters

For Usermanager the parameter that should not be null IUserStore<User> ==> the first parameter

so here how I did with minimum code

var _mockUserManager = new Mock<ApiUserManager>(new Mock<IUserStore<ApiUser>>().Object,
                null, null, null, null, null, null, null, null);
var _contextAccessor = new Mock<IHttpContextAccessor>();
var _userPrincipalFactory = new Mock<IUserClaimsPrincipalFactory<ApiUser>>();
Mock<ApiSignInManager>mockApiSignInManager = new Mock<ApiSignInManager>(_mockUserManager.Object,
               _contextAccessor.Object, _userPrincipalFactory.Object, null, null, null);

this response were inspired by umutesen on GitHub Forum

like image 7
Wajdi Chamakhi Avatar answered Nov 19 '22 19:11

Wajdi Chamakhi


The documentation you are looking at is for Microsoft.AspNet.Identity.Owin.SignInManager.

If you look at the error message, you will see that Moq is actually trying to create an object of type Microsoft.AspNetCore.Identity.SignInManager. Note the difference beetween AspNet and AspNetCore.

So basically, you were looking at the SignInManager that was used in the old Owin-based Identity stack for the classic ASP.NET. But you need to look at the ASP.NET Core version. If you look at the documentation of that type, you will see that the constructor has a few more dependencies:

public SignInManager(
    UserManager<TUser> userManager,
    IHttpContextAccessor contextAccessor,
    IUserClaimsPrincipalFactory<TUser> claimsFactory,
    IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> optionsAccessor,
    ILogger<Microsoft.AspNetCore.Identity.SignInManager<TUser>> logger,
    IAuthenticationSchemeProvider schemes);
like image 3
poke Avatar answered Nov 19 '22 19:11

poke