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

如何使用Jasmine Marbles测试含timer的NgRx Effect

用Jasmine Marbles测试带轮询逻辑的NgRx Effect

先修正原Effect的代码问题

原Effect中map操作符未正确解构withLatestFrom的结果,condition变量未定义,需先修复:

@Injectable()
export class YourEffects {
  timerEffect$ = createEffect(() =>
    this.actions$.pipe(
      ofType(fromActions.startTimerAction),
      switchMap(() =>
        timer(0, 1000).pipe(
          withLatestFrom(this.store.select(selectCondition)),
          // 解构获取selector返回的condition
          map(([_, condition]) => {
            if (condition) {
              return fromActions.timerTickAction1();
            }
            return fromActions.timerTickAction2();
          })
        )
      )
    )
  );

  constructor(private actions$: Actions, private store: Store) {} // 补充store注入
}

核心测试思路

Jasmine Marbles自带时间模拟机制,禁止和jasmine.clock()混用,否则会导致时间逻辑冲突。我们通过Marbles的时间语法(如1s代表1000ms)模拟轮询间隔,同时用provideMockStoremock store的selector状态。

完整测试代码示例

import { TestBed } from '@angular/core/testing';
import { provideMockStore, MockStore } from '@ngrx/store/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { hot, cold } from 'jasmine-marbles';
import { YourEffects } from './your.effects';
import * as fromActions from './your.actions';
import { selectCondition } from './your.selectors';

describe('YourEffects', () => {
  let effects: YourEffects;
  let actions$: Observable<any>;
  let store: MockStore;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        YourEffects,
        provideMockActions(() => actions$),
        provideMockStore()
      ]
    });

    effects = TestBed.inject(YourEffects);
    store = TestBed.inject(MockStore);
  });

  it('should emit timerTickAction2 immediately and every 1s when condition is false', () => {
    // Arrange
    store.overrideSelector(selectCondition, false);
    const startAction = fromActions.startTimerAction();
    const tickAction = fromActions.timerTickAction2();

    // 模拟10ms后触发启动action
    actions$ = hot('-a', { a: startAction });

    // 预期流:立即发射第一个tick,间隔990ms(凑满1s)后发射第二个
    const expected = cold('(b) 990ms b', { b: tickAction });

    // Assert
    expect(effects.timerEffect$).toBeObservable(expected);
  });

  it('should emit timerTickAction1 immediately and every 1s when condition is true', () => {
    // Arrange
    store.overrideSelector(selectCondition, true);
    const startAction = fromActions.startTimerAction();
    const tickAction = fromActions.timerTickAction1();

    actions$ = hot('-a', { a: startAction });
    const expected = cold('(b) 990ms b', { b: tickAction });

    // Assert
    expect(effects.timerEffect$).toBeObservable(expected);
  });
});

关键注意事项

  • Marbles与jasmine.clock()互斥:Marbles的hot/cold已内置时间模拟,手动调用jasmine.clock()会破坏其时间计算逻辑。
  • 处理无限流:timer是无限流,测试时只需验证前几次发射即可,通过Marbles语法限制输出次数。
  • Selector mock:用store.overrideSelector设置测试所需的selector返回值,确保Effect能获取正确状态。
  • 时间语法细节:(b)代表同步发射(对应timer的0延迟),990ms是为了抵消启动action的10ms延迟,凑满1秒的轮询间隔。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 00:45:10