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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:25:11