如何正确模拟带timer的Angular NgRX Effect以通过Karma测试?
NgRX Effect 定时调用场景的测试修复方案
问题背景
你编写的NgRX Effect通过timer(0, 10000)实现了每10秒调用一次dataService.loadNewData()的逻辑,但测试始终验证失败。原Effect代码如下:
loadData$: Observable<any> = createEffect((): Observable<Action> => { return this.actions$.pipe( ofType(loadDataStarted), switchMap(() => { return timer(0, 10000).pipe( switchMap(() => this.dataService.loadNewData().pipe( )), ); }), ); });
原测试代码的核心问题是没有触发Effect监听的loadDataStarted Action,同时错误处理了timer的立即执行逻辑,导致服务方法从未被调用:
beforeEach(() => { service.loadNewData.and.returnValue(of(data)); }); it('should call service', fakeAsync(() => { tick(10000) expect(service.loadNewData).toHaveBeenCalledOnceWith(); }));
正确的测试实现步骤
必须触发对应Action
Effect是通过监听loadDataStartedAction才会启动执行的,测试里必须手动派发这个Action。正确处理
timer(0)的立即执行timer(0, 10000)会在订阅后立即执行第一次调用,不需要等待10秒,用tick()即可触发;如果要验证后续的定时调用,再推进10秒。完整测试代码示例
beforeEach(() => { service.loadNewData.and.returnValue(of(data)); }); // 验证首次立即调用 it('should call service right after dispatching loadDataStarted', fakeAsync(() => { // 触发Effect的入口Action store.dispatch(loadDataStarted()); // 推进时间,处理timer的同步执行逻辑 tick(); // 验证方法被调用一次 expect(service.loadNewData).toHaveBeenCalledTimes(1); expect(service.loadNewData).toHaveBeenCalledWith(); })); // 验证定时重复调用 it('should call service every 10 seconds after initial call', fakeAsync(() => { store.dispatch(loadDataStarted()); // 首次调用 tick(); expect(service.loadNewData).toHaveBeenCalledTimes(1); // 推进10秒,触发第二次调用 tick(10000); expect(service.loadNewData).toHaveBeenCalledTimes(2); // 再推进10秒,触发第三次调用 tick(10000); expect(service.loadNewData).toHaveBeenCalledTimes(3); }));
额外注意事项
- 如果Effect在调用
loadNewData后有派发其他Action的逻辑,可以通过订阅store的actions流或者使用store.select()来验证这些Action是否正确派发。 - 使用
fakeAsync时,测试结束前建议调用flush()清空所有未处理的微任务,避免内存泄漏。
内容的提问来源于stack exchange,提问作者fefe
相关产品推荐
相关产品推荐

