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

如何用Jest为Angular的canActivate守卫编写单元测试并Mock Router

Jest替代Jasmine:Angular守卫canActivate方法Mock Router的实用方案

场景:在Angular项目中用Jest替代Jasmine编写守卫(TestServiceGuard)的canActivate方法单元测试,现有代码基于ng-mocks的MockBuilder构建测试环境,但Jest环境中没有Jasmine示例里的MockRouter类,不知道如何正确Mock Router并验证跳转逻辑。

方案一:基于ng-mocks扩展Router Mock(推荐,适配现有代码)

你已经在使用MockBuilder初始化测试环境,只需在mock Router时添加自定义的mock方法,或者用MockInstance动态覆盖Router的方法:

1. 全局初始化时Mock Router

修改beforeEach中的mock(Router)配置,直接注入带mock方法的对象:

describe('TestServiceGuard', () => {
  let service: TestServiceGuard;
  let router: jest.Mocked<Router>;

  beforeEach(() => {
    return MockBuilder(TestServiceGuard, TestModule)
      .mock(TranslateService, {
        instant: (value) => value,
      })
      // 给Router添加mock的navigate方法
      .mock(Router, {
        navigate: jest.fn().mockReturnValue(Promise.resolve(true)),
        // 如果需要用到createUrlTree,可添加:
        createUrlTree: jest.fn().mockReturnValue({} as UrlTree),
      });
  });

  beforeEach(() => {
    // 注入Router实例并断言为jest.Mocked类型,方便调用mock方法
    router = TestBed.inject(Router) as jest.Mocked<Router>;
    service = TestBed.inject(TestServiceGuard);
  });

  // 示例测试:验证守卫触发跳转
  it('should redirect to target path when condition is not met', () => {
    // 先mock依赖服务返回触发跳转的数据
    mockCurrentData(/* 传入会导致守卫跳转的测试数据 */);
    
    // 构造canActivate需要的参数(用ng-mocks的MockRoute快速生成)
    const route = MockRoute.create({});
    const state = MockRoute.state({});

    // 调用守卫方法
    service.canActivate(route, state);

    // 验证Router的navigate方法是否被正确调用
    expect(router.navigate).toHaveBeenCalledWith(['/expected-target-path']);
  });

  // 你原有的mockCurrentData方法保持不变
  function mockCurrentData(data: Fleet): jest.Mock {
    return MockInstance(OtherService, 'testData', jest.fn(), 'get')
      .mockReturnValue(of(data));
  }
});

2. 动态Mock Router方法(针对单测试用例)

如果需要在不同测试用例中给Router设置不同的mock行为,可以用MockInstance在测试内动态覆盖:

it('should handle navigation failure', () => {
  // 创建返回失败的navigate mock
  const navigateMock = jest.fn().mockReturnValue(Promise.reject(new Error('nav failed')));
  // 覆盖Router的navigate方法
  MockInstance(Router, 'navigate', navigateMock);

  // 执行测试逻辑
  mockCurrentData(/* 测试数据 */);
  const route = MockRoute.create({});
  const state = MockRoute.state({});
  
  service.canActivate(route, state);

  // 验证调用
  expect(navigateMock).toHaveBeenCalledTimes(1);
});

方案二:手动创建Router Mock对象(不依赖ng-mocks)

如果不用ng-mocks,直接在TestBed中提供自定义的Router mock:

describe('TestServiceGuard', () => {
  let service: TestServiceGuard;
  let router: jest.Mocked<Router>;

  beforeEach(() => {
    // 手动创建Router的mock对象
    router = {
      navigate: jest.fn().mockReturnValue(Promise.resolve(true)),
      createUrlTree: jest.fn().mockReturnValue({} as UrlTree),
    } as jest.Mocked<Router>;

    TestBed.configureTestingModule({
      providers: [
        TestServiceGuard,
        { provide: TranslateService, useValue: { instant: (v) => v } },
        { provide: Router, useValue: router },
        // 注册其他依赖服务
      ],
    });

    service = TestBed.inject(TestServiceGuard);
  });

  // 测试用例示例
  it('should redirect when condition fails', () => {
    mockCurrentData(/* 测试数据 */);
    service.canActivate({} as ActivatedRouteSnapshot, {} as RouterStateSnapshot);
    
    expect(router.navigate).toHaveBeenCalledWith(['/target']);
  });
});

关键注意点

  • Jest中没有MockRouter类,用jest.fn()创建的mock函数完全可以替代Jasmine的spyOn功能,用来追踪方法调用、设置返回值。
  • canActivate的参数ActivatedRouteSnapshot和RouterStateSnapshot可以用ng-mocks的MockRoute.create()/MockRoute.state()快速生成,或者直接断言为空对象(如果守卫不依赖参数中的具体数据)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 16:51:14