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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 14:01:16