Angular测试中combineLatest调用pipe提示undefined的问题排查
Angular 10服务测试报错排查
问题场景
服务代码
@Injectable() export class MyService { private readonly bSubject1$: BehaviorSubject<SomeType[]> = new BehaviorSubject<SomeType[]>(null); private readonly bSubject2$: BehaviorSubject<AnotherType[]> = new BehaviorSubject<AnotherType[]>(null); private readonly bSubject3$: BehaviorSubject<LastType[]> = new BehaviorSubject<LastType[]>(null); private readonly subscription$: Observable<SomeType[]>; private readonly bs4$: BehaviorSubject<string> = new BehaviorSubject<string>(''); private readonly bs5$ = new BehaviorSubject<boolean>(false); constructor(router: Router) { this.subscription$ = this.createStructure(); router.events .pipe(filter((event) => event instanceof NavigationEnd)) .subscribe((event: NavigationEnd) => { console.log('some code here'); }); } private createStructure() { const sub$ = combineLatest([this.bSubject1$, this.bSubject2$, this.bSubject3$]) .pipe(filter(...), map(...)); return combineLatest([sub$, this.bs4$, this.bs5$]) .pipe(map(...)); } }
测试代码
export class RouterMock {} describe('MyService', () => { let service: MyService; beforeEach(() => { TestBed.configureTestingModule({ providers: [ { provide: Router, useClass: RouterMock }, { provide: MyService, useClass: service }, ], }); service = TestBed.inject(MyService); }); it('should create', () => { console.log('service', service); // service is logged as `undefined` expect(service).toBeTruthy(); }); });
报错信息
测试在private createStructure() {行失败,报错:Failed: Cannot read properties of undefined (reading 'pipe'),同时控制台打印service为undefined。
问题原因及修复方案
1. MyService的Provider配置错误
你在TestBed的providers里写了{ provide: MyService, useClass: service },此时service是未初始化的变量(值为undefined),导致TestBed无法正确创建服务实例。
修复:直接将MyService加入providers数组即可:
TestBed.configureTestingModule({ providers: [ { provide: Router, useClass: RouterMock }, MyService // 直接添加服务类 ], });
2. RouterMock未模拟必要属性
RouterMock是空类,没有模拟events属性,服务构造函数中调用router.events.pipe(...)时,router.events为undefined,因此触发pipe调用报错。
修复:给RouterMock添加events属性,模拟为可观察对象:
export class RouterMock { // 模拟events为基础空Observable,满足基础调用需求 events = new Observable<NavigationEnd>(); }
如果需要测试导航事件逻辑,可以用BehaviorSubject手动触发事件:
export class RouterMock { private eventsSubject = new BehaviorSubject<NavigationEnd>(null); events = this.eventsSubject.asObservable(); // 提供方法手动发送导航事件 triggerNavigationEnd(url: string) { this.eventsSubject.next(new NavigationEnd(1, url, url)); } }
3. 可选优化:BehaviorSubject初始化值问题
你用null初始化了bSubject1$、bSubject2$、bSubject3$,虽然combineLatest会触发,但后续filter可能过滤掉null值,导致createStructure返回的Observable无输出。如果测试需要该流有值,可以在测试中手动给Subject发送测试数据:
// 测试中通过反射获取私有Subject并发送数据 const bSubject1 = (service as any).bSubject1$; bSubject1.next([/* 测试用SomeType数据 */]);
修复后的测试代码示例
export class RouterMock { events = new Observable<NavigationEnd>(); } describe('MyService', () => { let service: MyService; beforeEach(() => { TestBed.configureTestingModule({ providers: [ { provide: Router, useClass: RouterMock }, MyService ], }); service = TestBed.inject(MyService); }); it('should create', () => { expect(service).toBeTruthy(); }); });
内容的提问来源于stack exchange,提问作者Andrei
相关产品推荐
相关产品推荐

