如何使用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
相关产品推荐
相关产品推荐

