asp.net-coreasp.net-core-mvcasp.net-core-identity

ASP.NET Core 8.0 MVC & Identity : error when mocking, System.NotSupportedException: The default UI requires a user store with email support


I am creating unit tests using Moq to achieve 100% coverage for a class auto-generated by Microsoft Scaffold Identity, specifically Register.cs.cshtml. However, I’m encountering this error in some of my tests:

System.NotSupportedException : The default UI requires a user store with email support

Below are snippets of

  1. the log error,
  2. how I am setting the mock test,
  3. how my I configured the Microsoft Identity properties (Program.cs),
  4. the parameters of the RegisterModel, which is the class I need to test and
  5. the GetEmailStore() method where the error is happening.

The log error:

Error Message:

System.NotSupportedException : The default UI requires a user store with email support.

Stack Trace:
at EAM.Areas.Identity.Pages.Account.RegisterModel.GetEmailStore() in /home/ygorgsena/eam/Main/Areas/Identity/Pages/Account/Register.cshtml.cs:line 219
at EAM.Areas.Identity.Pages.Account.RegisterModel..ctor(SignInManager`1 signInManager, UserManager`1 userManager, IUserStore`1 userStore, ILogger`1 logger, IEmailSender emailSender) in /home/ygorgsena/eam/Main/Areas/Identity/Pages/Account/Register.cshtml.cs:line 44
at EAM.Tests.Unit.RegisterUnitTest..ctor() in /home/ygorgsena/eam/Tests/Unit/RegisterUnitTest.cs:line 91
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)

How I am setting up the mock:

public class RegisterModelTests
{
    private readonly Mock<UserManager<IdentityUser>> _userManagerMock;
    private readonly Mock<SignInManager<IdentityUser>> _signInManagerMock;
    private readonly Mock<IUserStore<IdentityUser>> _userStoreMock;
    private readonly Mock<IUserEmailStore<IdentityUser>> _emailStoreMock;
    private readonly Mock<ILogger<RegisterModel>> _loggerMock;
    private readonly Mock<IEmailSender> _emailSenderMock;
    private readonly RegisterModel _pageModel;

    public RegisterModelTests()
    {
        _userStoreMock = new Mock<IUserStore<IdentityUser>>();

        _userManagerMock = new Mock<UserManager<IdentityUser>>(
            _userStoreMock.Object,
            new Mock<IOptions<IdentityOptions>>().Object,
            new Mock<IPasswordHasher<IdentityUser>>().Object,
            Array.Empty<IUserValidator<IdentityUser>>(),
            Array.Empty<IPasswordValidator<IdentityUser>>(),
            new Mock<ILookupNormalizer>().Object,
            new Mock<IdentityErrorDescriber>().Object,
            new Mock<IServiceProvider>().Object,
            new Mock<ILogger<UserManager<IdentityUser>>>().Object);

        _signInManagerMock = new Mock<SignInManager<IdentityUser>>(
            _userManagerMock.Object,
            new Mock<IHttpContextAccessor>().Object,
            new Mock<IUserClaimsPrincipalFactory<IdentityUser>>().Object,
            new Mock<IOptions<IdentityOptions>>().Object,
            new Mock<ILogger<SignInManager<IdentityUser>>>().Object,
            new Mock<IAuthenticationSchemeProvider>().Object,
            new Mock<IUserConfirmation<IdentityUser>>().Object);

        _emailStoreMock = _userStoreMock.As<IUserEmailStore<IdentityUser>>();
        _loggerMock = new Mock<ILogger<RegisterModel>>();
        _emailSenderMock = new Mock<IEmailSender>();

        _pageModel = new RegisterModel(
            _userManagerMock.Object, _userStoreMock.Object, _signInManagerMock.Object, _loggerMock.Object, _emailSenderMock.Object);
    }

    [Fact]
    public void GetEmailStore_ThrowsNotSupportedException_WhenUserManagerDoesNotSupportEmail()
    {
        // Arrange
        _ = _userManagerMock.Setup(m => m.SupportsUserEmail).Returns(false);

        // Use reflection to access the private GetEmailStore method
        MethodInfo? methodInfo = typeof(RegisterModel).GetMethod("GetEmailStore", BindingFlags.NonPublic |                       BindingFlags.Instance);

        // Act & Assert
        Assert.NotNull(methodInfo);
        _ = Assert.Throws<NotSupportedException>(() => methodInfo.Invoke(_pageModel, null));
    }

How my I configured the Microsoft Identity properties in Program.cs:

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true).AddEntityFrameworkStores<EAMContext>();

The parameters of the RegisterModel class:

public class RegisterModel : PageModel
{
    private readonly SignInManager<IdentityUser> _signInManager;
    private readonly UserManager<IdentityUser> _userManager;
    private readonly IUserStore<IdentityUser> _userStore;
    private readonly IUserEmailStore<IdentityUser> _emailStore;
    private readonly ILogger<RegisterModel> _logger;
    private readonly IEmailSender _emailSender;

    public RegisterModel(
        UserManager<IdentityUser> userManager,
        IUserStore<IdentityUser> userStore,
        SignInManager<IdentityUser> signInManager,
        ILogger<RegisterModel> logger,
        IEmailSender emailSender)
    {
        _userManager = userManager;
        _userStore = userStore;
        _emailStore = GetEmailStore();
        _signInManager = signInManager;
        _logger = logger;
        _emailSender = emailSender;
    }

I tried to look at the source code to understand how the IUserEmailStore<TUser> works. The code is located at this link. This GetEmailStore() method is where the error is happening.

private IUserEmailStore<TUser> GetEmailStore()
{
    if (Store is not IUserEmailStore<TUser> emailStore)
    {
        throw new NotSupportedException(Resources.StoreNotIUserEmailStore);
    }

    return emailStore;
}

The Store field defined there at line 139 and is of type

protected internal IUserStore<TUser> Store { get; set; }

The GetEmailStore() method accesses an attribute of type IUserStore and checks if IUserEmailStore is there.

As you can see in the "How I am setting the mock" section, I tried mocking the IUserStore first and then the IUserEmailStore, but it doesn't seem to work.

Despite multiple attempts to resolve this, I’m still running into issues. I would really appreciate any help or insights on properly configuring the mock to include email support for testing. Thank you!


Solution

  • Answering my own question in case someone else has the same problem in the future:

    In the code provided, even though we extend the IUserStore to IEmailUserStore when mocking, we still need to manually set the boolean value that enables email support for DefaultUI in _userManagerMock before passing it as a parameter to _signInManager:

    _ = _userManagerMock.Setup(x => x.SupportsUserEmail)
        .Returns(true);