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

Jasmine单元测试中如何多次修改服务Observable状态并保证一致性

问题:Angular组件测试中Observable状态修改不稳定的解决方法

服务代码(BarService)

export class BarService {
  age: string = '';
  private dare$ = new BehaviorSubject<boolean>(false);
  delete() {
    throw new Error('Delete');
  }
  dare = this.dare$.asObservable();

  setDare(flag: boolean) {
    this.dare$.next(flag);
  }

  getVision() {
    // some http call 
    return of({ name: 'Andrew' });
  }
  // some http call 
  getVisions = of({ name: 'ion' });
}

组件消费代码

// ...
ngOnInit() {
  this._bar.getVisions
    .pipe(
      switchMap(() => this._bar.getVision()),
      tap((value: any) => (this.dare = value.name)),
      catchError((err) => {
        this.error = true;
        console.log(err);

        return EMPTY;
      })
    )
    .subscribe(console.log);
}
// ...

当前测试代码

beforeEach(() => {
  barService = jasmine.createSpyObj(
    'BarService',
    {
      getVision: of('Ion'),
    },
    {
      getVisions: getError(),
      age: '41',
      dare: of({}),
    }
  );
  TestBed.configureTestingModule({
    declarations: [BarComponent],
    providers: [
      // ...
      { provide: BarService, useValue: barService },
    ],
  }).compileComponents();

  fixture = TestBed.createComponent(BarComponent);
  component = fixture.componentInstance;
});

问题原因

测试结果不稳定的核心原因是:

  1. 组件的ngOnInit在TestBed.createComponent()执行时自动触发,此时getVisions和getVision的Observable已经完成订阅,后续在it块中修改Spy返回值无法影响已触发的订阅逻辑。
  2. 冷Observable的特性导致多次测试时,订阅时机的差异会引发状态不一致。

解决方案

方案一:每个测试用例独立初始化组件

将组件初始化逻辑移到it块中,确保设置好服务返回值后再触发ngOnInit:

let barService: jasmine.SpyObj<BarService>;
let fixture: ComponentFixture<BarComponent>;
let component: BarComponent;

beforeEach(() => {
  // 只初始化SpyObj的结构,不固定返回值
  barService = jasmine.createSpyObj('BarService', ['getVision'], ['getVisions', 'age', 'dare']);
  TestBed.configureTestingModule({
    declarations: [BarComponent],
    providers: [{ provide: BarService, useValue: barService }],
  });
});

it('测试正常流:getVisions正常返回,getVision返回Andrew', () => {
  // 先设置服务返回值
  barService.getVisions = of({ name: 'ion' });
  barService.getVision.and.returnValue(of({ name: 'Andrew' }));
  
  // 再初始化组件并触发ngOnInit
  fixture = TestBed.createComponent(BarComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();

  // 断言逻辑
  expect(component.dare).toBe('Andrew');
});

it('测试错误流:getVisions抛出错误', () => {
  barService.getVisions = throwError(() => new Error('Test Error'));
  barService.getVision.and.returnValue(of({ name: 'Ion' }));
  
  fixture = TestBed.createComponent(BarComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();

  expect(component.error).toBeTrue();
});

方案二:使用Subject动态控制Observable输出

通过Subject替代固定Observable,实现测试中动态发射值或错误:

let barService: jasmine.SpyObj<BarService>;
let fixture: ComponentFixture<BarComponent>;
let component: BarComponent;
let getVisionsSubject: Subject<any>;
let getVisionSubject: Subject<any>;

beforeEach(() => {
  getVisionsSubject = new Subject<any>();
  getVisionSubject = new Subject<any>();
  
  barService = jasmine.createSpyObj('BarService', {
    getVision: getVisionSubject.asObservable()
  }, {
    getVisions: getVisionsSubject.asObservable(),
    age: '41',
    dare: of({})
  });

  TestBed.configureTestingModule({
    declarations: [BarComponent],
    providers: [{ provide: BarService, useValue: barService }],
  });

  fixture = TestBed.createComponent(BarComponent);
  component = fixture.componentInstance;
});

it('测试正常流:动态发射值', () => {
  // 触发ngOnInit
  fixture.detectChanges();
  // 发射getVisions的正常值
  getVisionsSubject.next({ name: 'ion' });
  // 设置getVision的返回值
  barService.getVision.and.returnValue(of({ name: 'Andrew' }));

  expect(component.dare).toBe('Andrew');
});

it('测试错误流:动态发射错误', () => {
  fixture.detectChanges();
  getVisionsSubject.error(new Error('Test Error'));

  expect(component.error).toBeTrue();
});

关键注意事项

  • 确保每个测试用例完全独立,避免跨测试的状态污染
  • 必须在触发ngOnInit(即调用fixture.detectChanges())之前,设置好服务的Observable返回值
  • 对于需要多次发射值的场景,优先使用Subject来动态控制Observable的输出

内容的提问来源于stack exchange,提问作者NsdHSO

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 13:32:50