ASP.NET Core 8.0 MVC身份验证单元测试:模拟遇邮箱存储异常
问题解决:Moq测试Identity RegisterModel时的邮箱支持异常
问题现象
使用Moq为Microsoft Scaffold Identity自动生成的Register.cshtml.cs编写单元测试以实现100%覆盖率时,构造RegisterModel实例抛出异常:
System.NotSupportedException : The default UI requires a user store with email support
异常堆栈指向RegisterModel.GetEmailStore()方法,该方法检查Store是否实现IUserEmailStore<TUser>接口。
原因分析
RegisterModel构造函数中调用GetEmailStore(),该方法依赖Store属性实现IUserEmailStore<TUser>- 测试中虽尝试将
IUserStoreMock转换为IUserEmailStore,但未正确配置UserManager的邮箱支持标识,或Store属性未被正确赋值
解决方案
1. 修正测试配置代码
以下是可直接使用的测试类配置,包含邮箱支持的正确模拟:
public class RegisterModelTests { private readonly Mock<UserManager<IdentityUser>> _userManagerMock; private readonly Mock<SignInManager<IdentityUser>> _signInManagerMock; private readonly Mock<IUserStore<IdentityUser>> _userStoreMock; private readonly Mock<IEmailSender> _emailSenderMock; private readonly Mock<ILogger<RegisterModel>> _loggerMock; private RegisterModel _pageModel; public RegisterModelTests() { // 创建UserStore Mock并转换为IUserEmailStore _userStoreMock = new Mock<IUserStore<IdentityUser>>(); var emailStoreMock = _userStoreMock.As<IUserEmailStore<IdentityUser>>(); // 可选:模拟邮箱相关方法(根据测试需求添加) emailStoreMock.Setup(x => x.SetEmailAsync(It.IsAny<IdentityUser>(), It.IsAny<string>(), It.IsAny<CancellationToken>())) .Returns(Task.CompletedTask); emailStoreMock.Setup(x => x.GetEmailAsync(It.IsAny<IdentityUser>(), It.IsAny<CancellationToken>())) .ReturnsAsync("test@example.com"); // 创建UserManager Mock并启用邮箱支持 _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); _userManagerMock.Setup(m => m.SupportsUserEmail).Returns(true); // 创建SignInManager Mock _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); // 初始化其他依赖 _loggerMock = new Mock<ILogger<RegisterModel>>(); _emailSenderMock = new Mock<IEmailSender>(); // 实例化RegisterModel _pageModel = new RegisterModel( _userManagerMock.Object, _userStoreMock.Object, _signInManagerMock.Object, _loggerMock.Object, _emailSenderMock.Object); } [Fact] public void GetEmailStore_ThrowsNotSupportedException_WhenUserStoreLacksEmailSupport() { // Arrange:创建不支持邮箱的UserStore var nonEmailUserStore = new Mock<IUserStore<IdentityUser>>(); var testModel = new RegisterModel( _userManagerMock.Object, nonEmailUserStore.Object, _signInManagerMock.Object, _loggerMock.Object, _emailSenderMock.Object); // 反射调用私有方法 MethodInfo? methodInfo = typeof(RegisterModel).GetMethod("GetEmailStore", BindingFlags.NonPublic | BindingFlags.Instance); // Act & Assert Assert.NotNull(methodInfo); Assert.Throws<NotSupportedException>(() => methodInfo.Invoke(testModel, null)); } [Fact] public void Constructor_InitializesEmailStoreSuccessfully_WhenUserStoreSupportsEmail() { // Act & Assert:构造函数未抛出异常即表示初始化成功 Assert.NotNull(_pageModel); // 可选:验证_emailStore字段已赋值 var emailStoreField = typeof(RegisterModel).GetField("_emailStore", BindingFlags.NonPublic | BindingFlags.Instance); Assert.NotNull(emailStoreField?.GetValue(_pageModel)); } }
2. 检查RegisterModel构造函数
确认自动生成的RegisterModel构造函数中存在Store属性赋值语句,否则GetEmailStore()会因Store为null抛出异常:
public RegisterModel( UserManager<IdentityUser> userManager, IUserStore<IdentityUser> userStore, SignInManager<IdentityUser> signInManager, ILogger<RegisterModel> logger, IEmailSender emailSender) { _userManager = userManager; _userStore = userStore; Store = userStore; // 确保存在此行赋值 _emailStore = GetEmailStore(); _signInManager = signInManager; _logger = logger; _emailSender = emailSender; }
关键修正说明
- 必须设置
_userManagerMock.Setup(m => m.SupportsUserEmail).Returns(true),这是Identity默认UI判断邮箱支持的核心标识 - 使用
_userStoreMock.As<IUserEmailStore<IdentityUser>>()将基础存储Mock转换为邮箱存储接口,满足类型检查要求 - 测试异常场景时,需传入未转换为
IUserEmailStore的IUserStore实例,才能触发预期的NotSupportedException
内容的提问来源于stack exchange,提问作者Ygor G. Sena
相关产品推荐
相关产品推荐

