RxJS switchMap内Jasmine Spy异常:unconfirm未被调用
Angular单元测试中unconfirm spy未被捕获的问题
问题背景
我开发了一个带事件确认与撤销确认功能的Angular组件,功能运行正常,但编写单元测试时遇到异常:Jasmine能捕获confirm方法的spy调用,却无法捕获unconfirm的调用。控制台已输出tap操作中的日志"called",但测试断言失败。
核心组件代码
constructor(private service: Service, private snackbarService: MatSnackBar) {} confirm(id: number): void { this.service.confirm(id).pipe( switchMap(success => { const ref = this.snackbarService.open('Confirmed successfully', 'Undo confirm'); return forkJoin([of(success), ref.afterDismissed()]); }), filter(([cancelable, dismiss]) => cancelable && dismiss.dismissedByAction), switchMap(() => this.service.unconfirm(id)), tap(() => console.log('called')) ).subscribe(() => { this.snackbarService.open('Undone successfully', 'Close'); }); }
单元测试代码
beforeEach(async () => { const serviceMock = { confirm: (id: number) => of(true), unconfirm: (id: number) => of(true) }; await TestBed.configureTestingModule({ declarations: [TestComponent], imports: [ BrowserAnimationsModule, MatSnackBarModule ], providers: [ { provide: Service, useValue: serviceMock } ] }).compileComponents(); fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; }); it('when confirming and canceling action, should show action canceled success snackbar', () => { const service = TestBed.inject(Service); spyOn(service, 'confirm').and.callThrough(); spyOn(service, 'unconfirm').and.callThrough(); const id = 1; component.confirm(id); expect(service.confirm).toHaveBeenCalledOnceWith(id); let snackbar = document.querySelector('simple-snack-bar'); expect(snackbar).toBeTruthy(); snackbar?.querySelector('button')?.click(); fixture.detectChanges(); expect(service.unconfirm).toHaveBeenCalledOnceWith(id); });
报错信息
Expected spy unconfirm to have been called only once, and with given args:
[ 1 ]
But it was never called.
原因分析
问题核心在于异步流的处理逻辑:
- 组件中
ref.afterDismissed()返回的是异步Observable,点击撤销按钮后,该Observable的触发、后续的filter和switchMap操作都会进入异步队列执行。 - 原测试是同步执行的,点击按钮后立即执行断言,此时异步队列中的
unconfirm调用还未完成,导致spy未捕获到该调用;而测试结束后异步任务才执行,因此控制台能输出"called"但测试已判定失败。
解决方案
使用Angular测试提供的fakeAsync和tick来同步处理异步流,确保所有异步操作完成后再执行断言:
修改后的测试代码:
it('when confirming and canceling action, should show action canceled success snackbar', fakeAsync(() => { const service = TestBed.inject(Service); spyOn(service, 'confirm').and.callThrough(); spyOn(service, 'unconfirm').and.callThrough(); const id = 1; component.confirm(id); tick(); // 推进confirm的Observable流完成,渲染出snackbar expect(service.confirm).toHaveBeenCalledOnceWith(id); let snackbar = document.querySelector('simple-snack-bar'); expect(snackbar).toBeTruthy(); snackbar?.querySelector('button')?.click(); tick(); // 触发afterDismissed的Observable,推进整个流完成 fixture.detectChanges(); expect(service.unconfirm).toHaveBeenCalledOnceWith(id); }));
代码说明
fakeAsync:创建伪造的异步环境,将所有异步操作转为同步执行。tick():模拟时间流逝,触发所有待处理的异步任务,确保Observable链中的所有操作都执行完毕后再进行断言。
内容的提问来源于stack exchange,提问作者Carla C
相关产品推荐
相关产品推荐

