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

单元测试无法触发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);

关键修复说明

  1. 提前初始化Spy:在触发任何操作前创建spy,保证能捕获到后续的函数调用。
  2. 用switchMap管理异步流:将drop$的触发和处理嵌套在switchMap中,确保等待drop事件的处理逻辑完成后再执行断言。
  3. 添加错误捕获:在pipe末尾加入catchError,如果测试过程中出现错误,会调用done(err)通知测试框架,避免超时。
  4. 调整断言时机:确保在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:22:40