You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>接口。

原因分析

  1. RegisterModel构造函数中调用GetEmailStore(),该方法依赖Store属性实现IUserEmailStore<TUser>
  2. 测试中虽尝试将IUserStore Mock转换为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 13:35:55