如何在runInInjectionContext中测试带参数的Angular函数式路由守卫
Angular 15 函数式路由守卫(工厂函数)的正确测试方法
你的问题核心是没搞清楚工厂函数式守卫的调用流程:isLoggedIn是一个返回CanActivateFn的工厂函数,需要先传入redirectToLogin参数得到守卫函数,再给守卫函数传入路由快照参数才能执行,同时要在注入上下文里处理inject()调用。
修正后的测试代码
import { TestBed } from '@angular/core/testing'; import { CanActivateFn, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; import { CookieService } from 'ngx-cookie-service'; import { isLoggedIn } from './path-to-your-guard'; import { Router } from '@angular/router'; describe('isLoggedIn Guard', () => { let cookieServiceSpy: jasmine.SpyObj<CookieService>; let routerSpy: jasmine.SpyObj<Router>; let activatedRouteSnapshot: ActivatedRouteSnapshot; let routerStateSnapshot: RouterStateSnapshot; beforeEach(() => { // 初始化依赖的Spy对象 cookieServiceSpy = jasmine.createSpyObj<CookieService>(['check']); routerSpy = jasmine.createSpyObj<Router>(['createUrlTree']); // 模拟路由快照 activatedRouteSnapshot = { url: [], data: {}, } as ActivatedRouteSnapshot; routerStateSnapshot = { url: '/' } as RouterStateSnapshot; TestBed.configureTestingModule({ providers: [ { provide: CookieService, useValue: cookieServiceSpy }, { provide: Router, useValue: routerSpy } // 不需要把isLoggedIn加入providers,直接调用工厂函数即可 ], }); }); it('should return true when cookie is present', () => { // Given cookieServiceSpy.check.and.returnValue(true); // When // 1. 调用工厂函数得到守卫函数 const guard: CanActivateFn = isLoggedIn(false); // 2. 在注入上下文里执行守卫,传入快照参数 const result = TestBed.runInInjectionContext(() => guard(activatedRouteSnapshot, routerStateSnapshot) ); // Then expect(result).toBeTrue(); expect(cookieServiceSpy.check).toHaveBeenCalledWith('some_cookie'); }); it('should return false when cookie is missing and redirectToLogin is false', () => { // Given cookieServiceSpy.check.and.returnValue(false); // When const guard: CanActivateFn = isLoggedIn(false); const result = TestBed.runInInjectionContext(() => guard(activatedRouteSnapshot, routerStateSnapshot) ); // Then expect(result).toBeFalse(); }); it('should return UrlTree when cookie is missing and redirectToLogin is true', () => { // Given cookieServiceSpy.check.and.returnValue(false); const mockUrlTree = {} as UrlTree; routerSpy.createUrlTree.and.returnValue(mockUrlTree); // When const guard: CanActivateFn = isLoggedIn(true); const result = TestBed.runInInjectionContext(() => guard(activatedRouteSnapshot, routerStateSnapshot) ); // Then expect(result).toBe(mockUrlTree); expect(routerSpy.createUrlTree).toHaveBeenCalledWith( ['user', 'sign_in'], { queryParams: { next: encodeURIComponent('/') }, queryParamsHandling: 'merge', } ); }); });
关键修正点说明
工厂函数调用顺序:
先调用isLoggedIn(redirectToLogin)得到实际的守卫函数,再在注入上下文里执行这个函数并传入路由快照参数,而不是直接把工厂函数或守卫函数传给runInInjectionContext。依赖注入处理:
- 不需要将
isLoggedIn加入providers,因为它是工厂函数,不是需要注入的服务。 - 必须在
TestBed中提供守卫用到的所有依赖(CookieService、Router)的Spy对象,确保inject()能获取到依赖。
- 不需要将
路由快照模拟:
要给守卫传入完整的ActivatedRouteSnapshot和RouterStateSnapshot模拟对象,守卫内部会用到next.url等属性,所以需要按需初始化这些属性。
关于Route Data传参的补充
如果想改用Route Data传递redirectToLogin参数,守卫可以修改为:
export const isLoggedIn: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { const redirectToLogin = next.data['redirectToLogin'] ?? true; const isLoggedIn = !!inject(CookieService).check('some_cookie'); if (isLoggedIn) return true; if (!isLoggedIn && !redirectToLogin) return false; const redirectUrl = inject(Router).createUrlTree(['user', 'sign_in'], { queryParams: { next: encodeURIComponent(`/${next.url.join('/')}`) }, queryParamsHandling: 'merge', }); return redirectUrl; };
此时测试时只需要在activatedRouteSnapshot.data中设置redirectToLogin即可,不需要工厂函数参数,测试逻辑会更简单。
内容的提问来源于stack exchange,提问作者Florian Gösssele
相关产品推荐
相关产品推荐

