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

使用jasmine-marbles测试Promise转Observable的NGRX Effects问题

为什么返回Promise的Mock无法在Jasmine-Marbles测试NGRX Effects时生效?

这是个非常典型的虚拟时间测试与原生异步任务兼容性问题,我来帮你把底层逻辑和解决方案讲清楚:

问题根源:Jasmine-Marbles的虚拟时间 vs Promise的微任务

Jasmine-Marbles(本质是RxJS的TestScheduler)的核心是用虚拟时间同步执行Observable流——它会把所有Observable的事件调度都模拟在一个虚拟时间线上,不会真的等待实际的时间流逝,这样测试可以瞬间完成,且完全可控。

但Promise的异步回调是运行在浏览器/Node的微任务队列里的,这个队列的执行时机是在当前同步代码执行完之后,而Marble测试的断言是在虚拟时间推进完成后立刻执行的。当你在Effect里用from(this.backend.get(...))把Promise转成Observable时:

  1. Marble测试启动,推进虚拟时间到事件应该发生的点
  2. 此时Promise的resolve还没进入微任务队列执行,Observable还没发射任何值
  3. 测试立刻断言,发现Effect的流是空的,自然报错

而当你把Mock改成返回of({items: []})时,这个Observable是同步发射值的,完全在TestScheduler的虚拟时间控制范围内,所以Marble测试能准确捕获到发射的事件,测试通过。

解决方案:让Promise纳入虚拟时间控制

有几种可行的方案,你可以根据场景选择:

方案1:在Mock中用TestScheduler处理Promise

直接在Mock里把Promise转成由测试调度器控制的Observable,这样Promise的resolve会被纳入虚拟时间线:

import { TestScheduler } from 'rxjs/testing';

let testScheduler: TestScheduler;

beforeEach(() => {
  testScheduler = new TestScheduler((actual, expected) => {
    expect(actual).toEqual(expected);
  });

  const backend = {
    get: () => testScheduler.run(() => from(Promise.resolve({ items: [] })))
  };

  TestBed.configureTestingModule({
    providers: [{ provide: BackendServiceClient, useValue: backend }]
  });
});

方案2:在Effect中注入调度器(更规范的做法)

在Effect里显式指定调度器来处理Promise转Observable的逻辑,测试时注入TestScheduler:

修改Effect代码:

import { SchedulerLike, asyncScheduler, ASYNC_SCHEDULER } from 'rxjs';

@Injectable()
export class MissionsEffects {
  @Effect() loadMissions$ = this.dataPersistence.fetch(
    MissionsActionTypes.LoadMissions,
    {
      run: (action: LoadMissions, state: MissionsPartialState) => {
        // 用注入的调度器处理Promise
        return from(this.backend.get(new GetMissions()), this.scheduler).pipe(
          map(r => new MissionsLoaded(r.items))
        );
      },
      onError: (action: LoadMissions, error) => {
        console.error('Error', error);
        return new MissionsLoadError(error);
      }
    }
  );

  constructor(
    private dataPersistence: DataPersistence<MissionsPartialState>,
    private backend: BackendClientService,
    // 注入调度器,默认用asyncScheduler
    @Inject(ASYNC_SCHEDULER) private scheduler: SchedulerLike = asyncScheduler
  ) {}
}

测试时注入TestScheduler:

beforeEach(() => {
  testScheduler = new TestScheduler((actual, expected) => {
    expect(actual).toEqual(expected);
  });

  TestBed.configureTestingModule({
    providers: [
      { provide: BackendServiceClient, useValue: { get: () => Promise.resolve({ items: [] }) } },
      { provide: ASYNC_SCHEDULER, useValue: testScheduler }
    ]
  });
});

方案3:直接Mock成同步Observable(最简单)

如果你只是想测试Effect对后端返回值的处理逻辑,而非Promise的异步行为,直接把Mock改成返回of(...)就完全足够——这是测试中最常用的做法,因为我们不需要在Effect测试中验证Promise的异步特性,那是后端服务本身该测的内容。

总结

核心矛盾就是Promise的微任务脱离了Marble测试的虚拟时间控制,导致断言时异步结果还未生效。只要把Promise的执行纳入TestScheduler的虚拟时间线,或者换成同步Observable来Mock,就能解决问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:13:10