Angular测试报错:无法在初始化前访问UpdatesModalStore
问题背景
运行Angular测试时触发异常:
Error during loading: Uncaught ReferenceError: Cannot access 'UpdatesModalStore' before initialization
已通过npx madge --circular --extensions ts ./确认代码无循环依赖,注释掉localStorage的读写操作后测试可正常运行。
服务代码
import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, take } from 'rxjs'; import { MatSnackBar } from '@angular/material/snack-bar'; import { UpdateNotificationStorageEnum } from '../components/updates-modal/updates-modal.component'; import { UpdateTile } from '../components/updates-modal/update-tile/update-tile.component'; @Injectable({ providedIn: 'root' }) export class UpdatesModalStore { private updateNotificationCountBS$ = new BehaviorSubject<number>(0); private updatesOpenBS$ = new BehaviorSubject<boolean>(false); private updatesTilesBS$ = new BehaviorSubject<UpdateTile[]>([]); updateNotificationCount$ = this.updateNotificationCountBS$.asObservable(); updatesOpen$ = this.updatesOpenBS$.asObservable(); updatesTiles$ = this.updatesTilesBS$.asObservable(); constructor( private snackbar: MatSnackBar ) { } setUpdateNotificationCount(count: number): void { this.updateNotificationCountBS$.next(count); } setUpdatesOpen(open: boolean): void { this.updatesOpenBS$.next(open); } initUpdatesModal(): void { const notificationCount = localStorage.getItem(UpdateNotificationStorageEnum.UPDATE_NOTIFICATION_COUNT); if (!notificationCount) { this.setUpdateNotificationCount(1); localStorage.setItem(UpdateNotificationStorageEnum.UPDATE_NOTIFICATION_COUNT, '1'); } else { this.setUpdateNotificationCount(+notificationCount); } } toggleModal(): void { this.updatesOpen$.pipe(take(1)).subscribe((open: boolean) => { const newOpenState = !open; this.setUpdatesOpen(newOpenState); }); } setCurrentStep(steps: UpdateTile[]): void { this.updatesTilesBS$.next(steps); } setSmartSmartNotesEnabled(): void { localStorage.setItem(UpdateNotificationStorageEnum.UPDATE_NOTIFICATION_STEP, '2'); localStorage.setItem(UpdateNotificationStorageEnum.UPDATE_NOTIFICATION_COUNT, '2'); this.setUpdateNotificationCount(2); } }
报错原因
测试环境localStorage未正确模拟:Angular测试默认使用Jasmine/Karma,测试启动阶段服务初始化时,
localStorage可能还未被测试框架正确初始化或模拟。直接调用localStorage的读写方法会触发依赖链的提前执行,导致UpdatesModalStore在完成实例化前被引用。服务初始化时机冲突:如果服务通过
APP_INITIALIZER在应用启动时调用了initUpdatesModal,测试环境下这个执行时机可能早于服务完全初始化的节点。此时执行localStorage操作会间接触发组件或其他依赖对服务的引用,引发初始化顺序错误。隐性依赖的加载顺序问题:虽然madge未检测到循环依赖,但
UpdateNotificationStorageEnum所在的组件可能在模板或逻辑中注入了UpdatesModalStore,测试时模块加载顺序与生产环境不同,导致变量提升阶段出现服务未初始化就被引用的情况。
解决建议
- 测试中模拟localStorage:在测试文件的
beforeEach中添加localStorage的模拟,避免直接操作真实存储:
beforeEach(() => { spyOn(localStorage, 'getItem').and.returnValue('1'); spyOn(localStorage, 'setItem'); TestBed.configureTestingModule({ providers: [UpdatesModalStore] }); });
延迟localStorage操作时机:避免在服务构造函数或
APP_INITIALIZER中立即执行localStorage读写,改为在组件首次调用服务方法时执行,确保服务已完成初始化。排查隐性依赖:检查
UpdateNotificationStorageEnum所在组件是否依赖UpdatesModalStore,若有则调整组件的依赖注入方式,或在测试中先模拟该组件。
内容的提问来源于stack exchange,提问作者caden311

