单元测试无法触发RxJS流,done未执行引发超时错误
问题分析与修复
你的测试超时是因为expect断言失败后代码抛出错误,导致done()永远无法执行,同时还有几处RxJS和测试时序的问题:
核心问题点
- Spy创建时机错误:你在触发
drop$.emit()之后才创建spyObj,此时dropAudioElements已经执行完毕,spy根本捕获不到这次调用,直接导致第一个expect失败,代码中断,done()无法执行。 - 未处理异步操作:
drop$的处理逻辑可能是异步的(比如依赖RxJS异步流、Angular变更检测),同步断言会导致判断时机过早。 - 错误未捕获:如果
tap内代码抛出错误,订阅没有错误处理逻辑,会导致测试崩溃,done()同样无法触发。
修复后的测试代码
it('should drop element', (done) => { // 1. 提前创建Spy,确保能捕获到后续调用 const spyObj = jest.spyOn(component.media, 'dropAudioElements'); component.media.init$.pipe( filter(isInit => !!isInit), take(1), // 2. 用switchMap处理异步逻辑,等待drop操作完成 switchMap(_ => { console.log('hello'); fixture.detectChanges(); const dNDDirective = fixture.debugElement.query(By.directive(DnDClipDirective)); const drop$ = (dNDDirective.componentInstance as DnDClipDirective).drop$; // 先设置服务状态 component.filesService.isDragFromTree = true; component.filesService.draggedFile = { audio: new ArrayBuffer(0), url: 'test', filename: 'test' }; // 触发drop并返回事件流,等待处理完成 return drop$.pipe( take(1), tap(() => { // 3. 此时执行断言,确保异步逻辑已完成 expect(spyObj).toHaveBeenCalledTimes(1); expect(component.filesService.files).toHaveLength(1); console.log("HELLO 2"); done(); }) ); }), // 4. 添加错误处理,避免测试崩溃 catchError(err => { done(err); return EMPTY; }) ).subscribe(); console.log('HELLO'); component.media.init$.next(true); }, 6000);
关键修复说明
- 提前初始化Spy:在触发任何操作前创建spy,保证能捕获到后续的函数调用。
- 用switchMap管理异步流:将
drop$的触发和处理嵌套在switchMap中,确保等待drop事件的处理逻辑完成后再执行断言。 - 添加错误捕获:在pipe末尾加入
catchError,如果测试过程中出现错误,会调用done(err)通知测试框架,避免超时。 - 调整断言时机:确保在
drop$事件处理完成后再执行断言,保证数据已更新。
你也可以用fakeAsync/tick简化异步测试,摆脱done回调的依赖:
it('should drop element', fakeAsync(() => { const spyObj = jest.spyOn(component.media, 'dropAudioElements'); component.media.init$.next(true); tick(); // 处理init$的同步流 fixture.detectChanges(); const dNDDirective = fixture.debugElement.query(By.directive(DnDClipDirective)); component.filesService.isDragFromTree = true; component.filesService.draggedFile = { audio: new ArrayBuffer(0), url: 'test', filename: 'test' }; (dNDDirective.componentInstance as DnDClipDirective).drop$.emit({ event: <Event>{ target: {} }, files: [{ audio: new ArrayBuffer(0), url: 'test', filename: 'test' }] }); tick(); // 处理drop$的异步逻辑 expect(spyObj).toHaveBeenCalledTimes(1); expect(component.filesService.files).toHaveLength(1); }));
内容的提问来源于stack exchange,提问作者JSmith
相关产品推荐
相关产品推荐

