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

NgRx Effect Jest大理石测试中无法触发Tick的问题排查

NgRx Effect 测试未触发预期 Timeout Action 问题排查

问题描述

我编写了如下 NgRx Store Effect:

checkStatus$ = createEffect(() => {
    return this.actions$.pipe(
      ofType(Actions.checkstatus),
      fetch({
        run: ({ method }) => {
          const timeout: Observable<number> = interval(1000 * 60 * 2);
          // rest of code
          timeout.pipe(take(1)).subscribe(() => {
            return Actions.timeout();
          });
        }
      })
    );
  });

尝试用 Jest + TestScheduler 编写测试用例验证是否返回 timeout Action,测试代码如下:

it('should return timeout action', () => {
      testScheduler.run(helpers => {
        const { hot, cold, expectObservable } = helpers;

        actions$.next(Actions.checkstatus({method: method});

        expectObservable(effects.checktStatus$).toBe('120s (b|)', { b:  Actions.timeout() })
      });
    });

但测试结果不符合预期,实际返回空数组,预期输出如下:

- Expected  - 20
    + Received  +  1

    - Array [
    -   Object {
    -     "frame": 120000,
    -     "notification": Object {
    -       "error": undefined,
    -       "kind": "N",
    -       "value": Object {
    -         "type": "[action] timeOut",
    -       },
    -     },
    -   },
    -   Object {
    -     "frame": 120000,
    -     "notification": Object {
    -       "error": undefined,
    -       "kind": "C",
    -       "value": undefined,
    -     },
    -   },
    - ]
    + Array []

问题原因与解决方案

1. Effect 逻辑错误:手动订阅无法将 Action 注入输出流

你在 fetch 的 run 方法里手动调用 subscribe 处理 timeout 流,这种写法不会把 Actions.timeout() 传递到 Effect 的输出 Observable 中。NgRx Effect 要求 run 方法返回一个 Observable/Promise/单个值,这些结果会被自动订阅并将 Action 派发到 Store;手动订阅的结果完全脱离了 Effect 的流上下文,自然不会被捕获。

2. 测试代码的两处错误

  • 拼写错误:测试中引用的 Effect 名称是 checktStatus$,实际定义的是 checkStatus$(少了字母 s)
  • TestScheduler 使用错误:直接调用 actions$.next 无法被测试调度器捕获,应该用 hot 流模拟 Action 输入,确保所有异步操作都在调度器控制下。

修正后的 Effect 代码

将手动订阅改为返回 Observable,用 map 操作符将定时器事件转换为目标 Action:

checkStatus$ = createEffect(() => {
  return this.actions$.pipe(
    ofType(Actions.checkstatus),
    fetch({
      run: ({ method }) => {
        // 返回转换后的 Observable,由 Effect 自动处理订阅
        return interval(1000 * 60 * 2).pipe(
          take(1),
          map(() => Actions.timeout())
        );
      }
    })
  );
});

修正后的测试代码

it('should return timeout action', () => {
  testScheduler.run(({ hot, expectObservable }) => {
    // 用 hot 流模拟 Action 触发时机
    const mockActions$ = hot('-a', { 
      a: Actions.checkstatus({ method: method }) 
    });
    // 实例化 Effects 时传入模拟的 actions$
    const effects = new YourEffectsClassName(mockActions$, ...其他依赖项);

    // 验证输出流是否符合预期
    expectObservable(effects.checkStatus$).toBe('120s (b|)', { 
      b: Actions.timeout() 
    });
  });
});

额外注意事项

  • NgRx fetch 操作符会自动处理 run 方法返回的异步结果,无需手动订阅
  • 使用 TestScheduler 时,所有流操作必须通过调度器提供的 hot/cold 创建,不能混用手动推送事件的方式,否则调度器无法跟踪流的变化

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 09:37:04