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
Program.cs
),RegisterModel
, which is the class I need to test andGetEmailStore()
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!
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);