如何用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
相关产品推荐
相关产品推荐

