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

如何在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',
      }
    );
  });
});

关键修正点说明

  1. 工厂函数调用顺序:
    先调用isLoggedIn(redirectToLogin)得到实际的守卫函数,再在注入上下文里执行这个函数并传入路由快照参数,而不是直接把工厂函数或守卫函数传给runInInjectionContext。

  2. 依赖注入处理:

    • 不需要将isLoggedIn加入providers,因为它是工厂函数,不是需要注入的服务。
    • 必须在TestBed中提供守卫用到的所有依赖(CookieService、Router)的Spy对象,确保inject()能获取到依赖。
  3. 路由快照模拟:
    要给守卫传入完整的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 21:50:29