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

ABP 4.4.2应用测试模拟租户登录触发Autofac栈溢出如何解决

问题原因
  • 依赖生命周期不匹配:ABP框架原生的ICurrentTenant和ICurrentUser是*Scoped(范围)*生命周期,你在测试基类中将Mock的这两个服务注册为Singleton(单例),导致Autofac在解析多租户依赖、释放服务范围时出现生命周期校验的循环调用,最终触发栈溢出。
  • 服务解析时机错误:你在测试类的构造函数中就提前解析了IMenuAppService,此时还没有执行LoginAsXXX方法修改租户模拟值,且单例服务的依赖链在构造时已经固定,后续修改Mock值不会生效。
  • 缺少权限声明模拟:MenuAppService加了[Authorize]特性且配置了接口权限,你之前的Mock仅设置了租户ID和用户ID,没有给ICurrentUser添加对应的权限声明,即使解决栈溢出问题也会遇到401/403权限错误。
解决方案

步骤1:调整Mock服务的生命周期

修改测试基类的AfterAddApplication方法,将Mock服务的生命周期改为和原生一致的Scoped,同时移除原有默认注册:

protected override void AfterAddApplication(IServiceCollection services)
{
    // 替换ICurrentTenant注册,生命周期和原生一致为Scoped
    services.Replace(ServiceDescriptor.Scoped<ICurrentTenant>(sp => 
    {
        var fakeTenant = Substitute.For<ICurrentTenant>();
        return fakeTenant;
    }));
    
    // 替换ICurrentUser注册
    services.Replace(ServiceDescriptor.Scoped<ICurrentUser>(sp => 
    {
        var fakeUser = Substitute.For<ICurrentUser>();
        return fakeUser;
    }));
}

步骤2:优化登录模拟方法,增加权限声明

不再用全局字段存储Mock实例,每次从当前服务范围获取实例修改,同时补充权限声明:

protected void LoginAsHostAdmin()
{
    var currentTenant = GetRequiredService<ICurrentTenant>();
    currentTenant.Id.Returns(ci => null);
    currentTenant.Name.Returns(ci => null);
    
    var currentUser = GetRequiredService<ICurrentUser>();
    currentUser.Id.Returns(ci => TestData.HostAdminUserId);
    currentUser.TenantId.Returns(ci => null);
    // 补充权限声明,匹配应用服务所需的权限
    currentUser.Claims.Returns(ci => new List<Claim>
    {
        new Claim(AbpClaimTypes.UserId, TestData.HostAdminUserId.ToString()),
        new Claim(AbpClaimTypes.Role, "admin"),
        new Claim(OrderManagementPermissions.MENUS_MANAGEMENT, "true"),
        new Claim(OrderManagementPermissions.MENUS_MANAGEMENT_LIST, "true")
    });
}

protected void LoginAsDefaultTenant()
{
    var tenantId = Guid.Parse(OrderManagementTestData.DefaultTenantId);
    var currentTenant = GetRequiredService<ICurrentTenant>();
    currentTenant.Id.Returns(ci => tenantId);
    currentTenant.Name.Returns(ci => TestData.DefaultTenantName);
    
    var currentUser = GetRequiredService<ICurrentUser>();
    currentUser.Id.Returns(ci => TestData.DefaultTenantAdminUserId);
    currentUser.TenantId.Returns(ci => tenantId);
    currentUser.Claims.Returns(ci => new List<Claim>
    {
        new Claim(AbpClaimTypes.UserId, TestData.DefaultTenantAdminUserId.ToString()),
        new Claim(AbpClaimTypes.TenantId, tenantId.ToString()),
        new Claim(AbpClaimTypes.Role, "admin"),
        new Claim(OrderManagementPermissions.MENUS_MANAGEMENT, "true"),
        new Claim(OrderManagementPermissions.MENUS_MANAGEMENT_LIST, "true")
    });
}

步骤3:修改测试类的服务解析时机

不要在构造函数中解析应用服务,改为每个测试用例完成登录模拟后再解析:

public class MenusAppService_Tests : OrderManagementApplicationTestBase
{
    [Fact]
    public async Task GetListAsync_Filter_Tests()
    {
        // Arrange
        LoginAsDefaultTenant();
        // 登录完成后再解析应用服务
        var menuAppService = GetRequiredService<IMenuAppService>();
        var listRequestDto = new ListMenuRequestDto
        {
            RestaurantId = Guid.Parse(OrderManagementTestData.DefaultRestaurantId)
        };

        // Act
        var list = await menuAppService.GetListAsync(listRequestDto);

        // Assert
        list.ShouldNotBeNull();
        list.Items.Count.ShouldBe(2);
        list.Items.ShouldContain(m => m.Title == TestData.MenuKebab.Title);
        list.Items.ShouldContain(m => m.Title == TestData.MenuKotelet.Title);
    }
}

可选简化方案

如果不需要自定义Mock用户的其他属性,可直接使用ABP框架自带的ICurrentTenant.Change方法切换租户,配合框架内置的测试身份模拟方案,无需手动MockICurrentTenant,更不易出错。

内容的提问来源于stack exchange,提问作者Mahdi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 00:27:03