如何为依赖createUrlTreeFromSnapshot的Angular17 CanActivateFn守卫编写Jest测试
Angular 17.1 CanActivateFn 守卫单元测试解决方案
问题根源
createUrlTreeFromSnapshot需要完整的路由快照上下文(包含parent、router等核心属性),直接传入空的ActivatedRouteSnapshot会因缺少必要字段抛出Cannot read properties of undefined (reading 'children')错误。
正确测试实现步骤
1. 初始化测试环境与依赖模拟
使用TestBed配置测试模块,模拟CoreFacade和路由相关依赖,构建具备完整上下文的ActivatedRouteSnapshot:
import { TestBed } from '@angular/core/testing'; import { CanActivateFn, ActivatedRouteSnapshot, Router, UrlTree } from '@angular/router'; import { showPensionInfoGuard } from './allow-navi.guard'; import { CoreFacade } from '@mylib/core'; import { of } from 'rxjs'; describe('showPensionInfoGuard', () => { let guard: CanActivateFn; let mockCoreFacade: jasmine.SpyObj<CoreFacade>; let mockRouter: jasmine.SpyObj<Router>; let mockRoute: ActivatedRouteSnapshot; beforeEach(() => { // 模拟CoreFacade,动态控制allowNavigation$的返回值 mockCoreFacade = jasmine.createSpyObj('CoreFacade', [], { allowNavigation$: of(null) }); // 模拟Router,拦截createUrlTree调用 mockRouter = jasmine.createSpyObj('Router', ['createUrlTree']); // 构建带parent的路由快照,支持相对路径解析 const parentRoute = new ActivatedRouteSnapshot(); parentRoute.url = [{ path: 'parent' }]; mockRoute = new ActivatedRouteSnapshot(); mockRoute.router = mockRouter; mockRoute.parent = parentRoute; TestBed.configureTestingModule({ providers: [ { provide: CoreFacade, useValue: mockCoreFacade }, { provide: Router, useValue: mockRouter } ] }); guard = TestBed.inject(showPensionInfoGuard); }); });
2. 测试允许导航场景(allowNavigation$ 返回 false)
it('允许导航当 allowNavigation$ 返回 false', (done) => { mockCoreFacade.allowNavigation$ = of(false); guard(mockRoute).subscribe({ next: (result) => { expect(result).toBe(true); done(); }, error: (err) => done.fail(err) }); });
3. 测试跳转路由场景(allowNavigation$ 返回 true)
验证createUrlTreeFromSnapshot是否正确生成指向../main的UrlTree,通过拦截Router的createUrlTree方法断言调用参数:
it('返回指向 ../main 的 UrlTree 当 allowNavigation$ 返回 true', (done) => { mockCoreFacade.allowNavigation$ = of(true); const mockUrlTree = {} as UrlTree; mockRouter.createUrlTree.and.returnValue(mockUrlTree); guard(mockRoute).subscribe({ next: (result) => { expect(mockRouter.createUrlTree).toHaveBeenCalledWith(['../main'], { relativeTo: mockRoute }); expect(result).toBe(mockUrlTree); done(); }, error: (err) => done.fail(err) }); });
4. 测试null值过滤逻辑
确保守卫会忽略allowNavigation$发出的null值,仅处理非null的布尔状态:
it('忽略 allowNavigation$ 发出的 null 值', (done) => { let isEmitted = false; mockCoreFacade.allowNavigation$ = of(null, false); guard(mockRoute).subscribe({ next: (result) => { isEmitted = true; expect(result).toBe(true); done(); } }); setTimeout(() => { if (!isEmitted) done.fail('守卫未在收到非null值后触发输出'); }, 100); });
核心注意事项
- 必须给
ActivatedRouteSnapshot设置router和parent属性,否则相对路径解析会失败 - 动态切换
mockCoreFacade.allowNavigation$的返回值,覆盖所有业务场景 - 异步测试必须使用
done回调,确保Observable完成后再断言
内容的提问来源于stack exchange,提问作者SuneRadich
相关产品推荐
相关产品推荐

