简体   繁体   中英

How do I unit test ASP .NET Core 3.0+ Razor Page handlers that require UserManager<TUser> and SignInManager<TUser>?

I am trying to simply test my Register.cshtml.cs OnGet() method. The PageModel requires a UserManager<TUser> and a SignInManager<TUser> . I need to mock the SignInManager<TUser>.IsSignedIn(ClaimsPrincipal user) method which requires those classes. The problem is the constructors for those classes in ASP .NET Core are much more complicated than most solutions document for previous ASP .NET versions. The only constructor for UserManager has nine required parameters, and I believe SignInManager required seven.

I was able to get far enough for my test to execute but the mocked method is not executing.

My test:

[Fact]
public void OnGet_UserIsSignedIn_RedirectsToIndex()
{
    using (var testContext = new ApplicationDbContext(TestHelper.TestApplicationDbContextOptions()))
    {
        // Arrange
        var mockUserStore = new Mock<IUserStore<AppUser>>();

        var userManager = new UserManager<AppUser>(mockUserStore.Object,  null, null, null, null, null, null, null, null);

        var mockSignInManager = new Mock<SignInManager<AppUser>>(
            userManager,
            new HttpContextAccessor(),
            new Mock<IUserClaimsPrincipalFactory<AppUser>>().Object,
            new Mock<IOptions<IdentityOptions>>().Object,
            new Mock<ILogger<SignInManager<AppUser>>>().Object,
            new Mock<IAuthenticationSchemeProvider>().Object,
            new Mock<IUserConfirmation<AppUser>>().Object
            );

        var claimsPrincipal = new TestPrincipal(new Claim("name", "John Doe"));
        var _mockConfig = new Mock<IConfiguration>();
        var _mockUserValidator = new UserValidator<AppUser>();
        var _mockLogger = new Mock<ILogger<RegisterModel>>();
        var _mockEmailSender = new Mock<IEmailSender>();

        var registerModel = new RegisterModel(_mockConfig.Object, userManager, mockSignInManager.Object, _mockUserValidator, _mockLogger.Object, _mockEmailSender.Object);

        mockSignInManager.Setup(m => m.IsSignedIn(claimsPrincipal)).Returns(true);
        var expected = new RedirectToPageResult("/Index");

        // Act
        var result = registerModel.OnGet();

        //Assert
        Assert.Equal(expected, result);
    }

}

OnGet method:

public IActionResult OnGet(string returnUrl = null)
{
    // The test is supposed to result in the code inside of this IF executing
    // but it continues on and fails at CreateGateway() because it is missing
    // dependencies for that method.
    if (_signInManager.IsSignedIn(User))
        return new RedirectToPageResult("/Index");

    ReturnUrl = returnUrl;

    // Create gateway
    Gateway = CreateGateway();

    // Load clientToken
    ClientToken = Gateway.ClientToken.Generate();

    return Page();
}

Error:

Error Message:
   System.NullReferenceException : Object reference not set to an instance of an object.
  Stack Trace:
     at Project.Areas.Identity.Pages.Account.RegisterModel.CreateGateway() in ... line 150

RegisterModel constructor:

public RegisterModel(
    IConfiguration config,
    UserManager<AppUser> userManager,
    SignInManager<AppUser> signInManager,
    UserValidator<AppUser> userValidator,
    ILogger<RegisterModel> logger,
    IEmailSender emailSender)
{
    _config = config;
    _userManager = userManager;
    _signInManager = signInManager;
    _userValidator = userValidator;
    _logger = logger;
    _emailSender = emailSender;
}

Not only does my test feel over-complicated but it is failing to trigger the IsSignedIn() mocked method and fails on a later line due to a lack of dependecies for the CreateGateway() method.

Simply: How do I arrange a test for a PageModel handler requiring a UserManager<TUser> and SignInManager<TUser> for ASP .NET Core 3.0 or later ?

The issue in this case, is that the User in the code under test

if (_signInManager.IsSignedIn(User))

does not match the principal configured in Setup

var claimsPrincipal = new TestPrincipal(new Claim("name", "John Doe"));

//...

mockSignInManager.Setup(m => m.IsSignedIn(claimsPrincipal)).Returns(true);

Which results in the mock not behaving as expected when the test is exercised and the condition evaluated.

I suggest loosening up the expectation using It.IsAny<>()

mockSignInManager
    .Setup(_ => _.IsSignedIn(It.IsAny<ClaimsPrincipal>()))
    .Returns(true);

Ideally I would have added an additional layer of abstraction to the PageModel to simplify its dependency on all those implementation concerns.

You should also refer to available documentation for tips on how to unit test razor pages

Reference Razor Pages unit tests in ASP.NET Core

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM