Angular17函数式Resolver在Jest测试中无法注入服务的问题
Angular v14 迁移至v17:ResolverFn 测试时触发注入上下文错误
概述
正在将Angular应用从v14迁移至v17,需将已弃用的旧Resolver类替换为新的ResolverFn实现。运行测试代码时,调用inject()会触发inject() must be called from an injection context错误。
旧解析器
@Injectable() export class TimespanResolver { private memberGranularity$: Observable<GranularityType>; constructor( private timespanService: TimespanService, private coreCache: CoreCacheService ) { this.memberGranularity$ = this.coreCache .MemberSettings() .pipe( map((settings) => settings == null ? GranularityType.Weekly : settings.DefaultGranularity ) ); } /** * 路由变更时更新时间范围 * @param route -- 路由快照 */ resolve({ data }: ActivatedRouteSnapshot): Promise<Timespan> { const granularity$ = data.initGranularity ? of(data.initGranularity) : this.memberGranularity$; return firstValueFrom( granularity$.pipe( map((granularity) => TimespanService.GetTimespanFromGranularity(granularity) ), tap((timespan) => this.timespanService.UpdateTimespan( timespan, data.timespanDisplayText ) ) ) ); } }
新解析器
export const timespanResolver: ResolveFn<any> = ( route: ActivatedRouteSnapshot, __: RouterStateSnapshot ) => { const coreCache = inject(CoreCacheService); // 触发注入错误 const timespanService = inject(TimespanService); // 触发注入错误 let memberGranularity$: Observable<GranularityType>; memberGranularity$ = coreCache .MemberSettings() .pipe( map((settings) => settings == null ? GranularityType.Weekly : settings.DefaultGranularity ) ); const granularity$ = route.data.initGranularity ? of(route.data.initGranularity) : memberGranularity$; return firstValueFrom( granularity$.pipe( map((granularity) => TimespanService.GetTimespanFromGranularity(granularity) ), tap((timespan) => timespanService.UpdateTimespan( timespan, route.data.timespanDisplayText ) ) ) ); };
当前测试代码
it('should update with default granularity from user settings if one isnt specified', fakeAsync(() => { // 准备 jest.spyOn(service, 'UpdateTimespan'); const newMoment = Date.UTC(2019, 1, 0); const realDateNow = Date.now.bind(global.Date); const dateNowStub = jest.fn(() => newMoment.valueOf()); global.Date.now = dateNowStub; TimespanService._masterMomentRef = moment(); jest.spyOn(TimespanService, 'GetTimespanFromGranularity'); let snapshot: ActivatedRouteSnapshot = { data: {} } as ActivatedRouteSnapshot; let stateSnap: RouterStateSnapshot = { } as RouterStateSnapshot; // 执行 timespanResolver(snapshot, stateSnap); flush(); let res: Timespan; service.Timespan$.subscribe(t => res = t); flush(); let startDate = res.StartDate.year() !== res.EndDate.year() ? res.StartDate.format('MMMM Do, YYYY') : res.StartDate.format('MMMM Do'); let endDate = res.EndDate.month() !== res.StartDate.month() ? res.EndDate.format('MMMM Do, YYYY') : res.EndDate.format('Do, YYYY'); // 断言 expect(service.UpdateTimespan).toHaveBeenCalledTimes(1); expect(TimespanService.GetTimespanFromGranularity).toHaveBeenCalledWith(GranularityType.Weekly); expect(res.Granularity).toBe(Weekly); expect(res.DisplayDate).toMatch(`${ startDate } - ${ endDate }`); global.Date.now = realDateNow; TimespanService._masterMomentRef = moment(); }));
已尝试的方案
- 尝试在lambda表达式参数中注入(仍出现相同错误):
export const timespanResolver: ResolveFn<any> = ( route: ActivatedRouteSnapshot, __: RouterStateSnapshot, coreCache = inject(CoreCacheService), timespanService = inject(TimespanService) ) => { // 代码省略 }
- 尝试添加类来存放注入的服务(构造函数属于注入上下文,但仍出现相同错误):
export const timespanResolver: ResolveFn<any> = ( route: ActivatedRouteSnapshot, __: RouterStateSnapshot ) => { let services = new ResolverServices(); let coreCache = services.coreCache; let timespanService = services.timespanService; // 剩余代码省略 }; class ResolverServices() { public coreCache = inject(CoreCacheService); public timespanService = inject(TimespanService); constructor() { } }
- 曾寻找从解析器获取Injector以调用runInInjection的方法,但未找到示例。
内容的提问来源于stack exchange,提问作者IMeyers20
相关产品推荐
相关产品推荐

