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

使用RouterTestingHarness测试Angular函数式守卫时内存溢出问题排查

Angular Jest测试函数式守卫时内存溢出问题排查与解决

问题场景

使用Jest测试Angular函数式守卫,通过RouterTestingHarness模拟路由时,触发内存溢出错误:

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

待测试的函数式守卫代码

export const guard: CanActivateFn = () => {
  const service = inject(service);
  const router = inject(Router);
  return service.loginInfo().pipe(
    map((res) => {
      if (condition1) {
        return router.parseUrl(pathA);
      }
      if (condition2) {
        return router.parseUrl(pathB);
      }
      return router.parseUrl(Fail);
    }),
  );
};

原测试代码

@Component({ template: '' })
export class DummyComponent {}

describe('Test Guard', () => {
  let service = {
    getInfo: jest.fn(),
  }; //mocking service

  beforeEach(async () => {
    TestBed.configureTestingModule({
      providers: [
        provideRouter([
          { path: PathA, component: DummyComponent, canActivate: [guard] },
          { path: PathB, component: DummyComponent },
          { path: Fail, component: DummyComponent },
        ]),
        {
          provide: service,
          useValue: service,
        },
      ],
    });
    service = TestBed.inject(service);
  });

  const spy = jest.spyOn(service, 'loginInfo');

  it('test1', async () => {
    spy.mockReturnValue(of(mockLogin()));
    await RouterTestingHarness.create(PathA);
    const url = TestBed.inject(Router).url;
    expect(url).toEqual('PathA);
  });
});

问题根源分析

  1. 路由无限循环:测试时直接访问PathA,而该路由被当前守卫保护。当守卫逻辑返回跳转到PathA时,会再次触发守卫校验,形成无限循环,持续占用内存直到溢出。
  2. Spy创建位置错误:jest.spyOn放在describe块顶级作用域,会在所有测试执行前创建且不会被清理,多次测试会积累无用的spy实例,加剧内存消耗。
  3. 语法与匹配错误:
    • 模拟的service方法是getInfo,但守卫中调用的是loginInfo,导致mock失效
    • expect(url).toEqual('PathA)缺少闭合引号,属于语法错误

修复方案

1. 避免路由循环

添加一个不带守卫的测试入口路由,通过访问该路由触发守卫跳转,而非直接访问被守卫保护的目标路由。

2. 正确管理Spy

将spy的创建移至beforeEach或单个测试用例中,确保每次测试后能被清理。

3. 修复语法与匹配问题

确保service的mock方法名与守卫中调用的一致,修正字符串语法错误。

修复后的测试代码

@Component({ template: '' })
export class DummyComponent {}

describe('Test Guard', () => {
  let mockService: { loginInfo: jest.Mock };
  let spy: jest.SpyInstance;

  beforeEach(async () => {
    // 初始化mock服务,方法名与守卫保持一致
    mockService = {
      loginInfo: jest.fn(),
    };

    await TestBed.configureTestingModule({
      providers: [
        provideRouter([
          // 添加无守卫的测试入口路由
          { path: 'test-entry', component: DummyComponent, canActivate: [guard] },
          { path: PathA, component: DummyComponent },
          { path: PathB, component: DummyComponent },
          { path: Fail, component: DummyComponent },
        ]),
        {
          provide: YourService, // 替换为实际的服务类(不是对象)
          useValue: mockService,
        },
      ],
    }).compileComponents();

    // 在TestBed初始化后创建spy
    spy = jest.spyOn(mockService, 'loginInfo');
  });

  it('should redirect to PathA when condition1 is met', async () => {
    // 返回满足condition1的mock数据
    spy.mockReturnValue(of({ /* 满足condition1的结构 */ }));
    // 访问测试入口路由,触发守卫
    const harness = await RouterTestingHarness.create('/test-entry');
    const router = TestBed.inject(Router);
    // 断言最终跳转的URL
    expect(router.url).toEqual(`/${PathA}`);
  });
});

额外优化建议

  • 使用jest.clearAllMocks()在afterEach中清理mock调用记录,避免测试间污染
  • 若仍存在内存问题,可调整Jest的内存限制,在测试命令中添加--max-old-space-size=4096(根据需要调整数值)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 10:07:14