Angular 15单元测试中触发组件订阅的Action问题
在组件中订阅的Action的触发测试
问题背景
我有一个Angular 15项目,在多个组件中订阅了特定事件。我尝试在单元测试中触发已被订阅的Action,以验证组件中产生的特定效果。但触发loadProcessorsForGroupSuccess Action时,组件的订阅从未输出日志,也没有执行相应操作。
组件中的Action监听器
this.actions$.pipe( ofType( loadProcessorsForGroupSuccess, loadProcessorsForGroupFailure ) ).subscribe(() => { console.log('update success') this.spinner.hide('processing') });
当前Jest单元测试代码
beforeAll(() => MockBuilder(ProcessingComponent, AppModule) .provide(provideMockStore({ initialState, selectors: [ ...my selector definitions ] })) .provide(provideMockActions(() => actions$)) .mock(NgxSpinnerService, { show: jest.fn(), hide: jest.fn() }) .mock(NgbOffcanvas) .mock(ProcessorsService) .keep(UpperCasePipe) ); beforeEach(() => { fixture = MockRender(ProcessingComponent); component = fixture.point.componentInstance; store = ngMocks.findInstance(MockStore); spinnerService = ngMocks.findInstance(NgxSpinnerService); testScheduler = new TestScheduler((actual, expected) => { expect(actual).toEqual(expected); }); }); fit('should take action on [loadProcessorsForGroupSuccess]', () => { const action = loadProcessorsForGroupSuccess({ processors: [] as any, gType: 'processing' }); const spinnerSpy = jest.spyOn(spinnerService, 'show'); store.dispatch(action); });
解决方案
问题核心是provideMockActions传入的actions$没有正确发出Action。使用NgRx测试工具时,需要用Subject作为actions$的源,手动推送Action到流中。
步骤1:正确初始化actions$
在测试文件顶部定义Subject,作为可观察流的推送源:
import { Subject } from 'rxjs'; import { provideMockActions } from '@ngrx/effects/testing'; let actions$: Subject<any>; beforeAll(() => { actions$ = new Subject(); return MockBuilder(ProcessingComponent, AppModule) .provide(provideMockStore({ initialState, selectors: [ ...my selector definitions ] })) .provide(provideMockActions(() => actions$)) // 传入Subject .mock(NgxSpinnerService, { show: jest.fn(), hide: jest.fn() }) .mock(NgbOffcanvas) .mock(ProcessorsService) .keep(UpperCasePipe); });
步骤2:推送Action并触发变更检测
直接调用store.dispatch不会让mock的actions$流发出事件,需要手动推送Action到actions$,同时触发Angular变更检测:
fit('should take action on [loadProcessorsForGroupSuccess]', () => { const action = loadProcessorsForGroupSuccess({ processors: [] as any, gType: 'processing' }); const spinnerHideSpy = jest.spyOn(spinnerService, 'hide'); // 推送Action到订阅流 actions$.next(action); // 触发变更检测,确保组件响应 fixture.detectChanges(); // 验证预期操作执行 expect(spinnerHideSpy).toHaveBeenCalledWith('processing'); });
通用测试方法(多组件复用)
封装工具函数简化重复操作:
/** * 触发组件订阅的NgRx Action并触发变更检测 * @param action 要触发的Action * @param fixture 组件Fixture实例 * @param actions$ 测试用的Actions Subject */ export function triggerNgRxAction(action: any, fixture: any, actions$: Subject<any>) { actions$.next(action); fixture.detectChanges(); }
其他组件测试中直接调用:
fit('should respond to xxxAction', () => { const action = xxxAction(/* 传入Action参数 */); const serviceSpy = jest.spyOn(targetService, 'targetMethod'); triggerNgRxAction(action, fixture, actions$); expect(serviceSpy).toHaveBeenCalled(); });
额外注意事项
- 若组件订阅包含异步操作(如延迟、
async管道),需用fakeAsync和tick()处理:
fit('should take action on async subscription', fakeAsync(() => { const action = loadProcessorsForGroupSuccess({ processors: [] as any, gType: 'processing' }); const spinnerHideSpy = jest.spyOn(spinnerService, 'hide'); actions$.next(action); tick(); // 处理RxJS异步流 fixture.detectChanges(); expect(spinnerHideSpy).toHaveBeenCalledWith('processing'); }));
- 测试后清理资源,避免内存泄漏:
afterEach(() => { actions$.complete(); });
内容的提问来源于stack exchange,提问作者millerbill3
相关产品推荐
相关产品推荐

