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
相关产品推荐
相关产品推荐

