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

