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
相关产品推荐
相关产品推荐

