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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 13:54:50