NGXS:如何测试一个动作是不是被调度?

Posted

技术标签:

【中文标题】NGXS:如何测试一个动作是不是被调度?【英文标题】:NGXS: How to test if an action was dispatched?NGXS:如何测试一个动作是否被调度? 【发布时间】:2018-12-07 20:42:35 【问题描述】:

如何对一个动作是否被调度进行单元测试?

例如,在一个 LogoutService 中,我有这个简单的方法:

  logout(username: string) 
    store.dispatch([new ResetStateAction(), new LogoutAction(username)]);
  

我需要编写一个单元测试来验证这两个动作是否被调度:

  it('should dispatch ResetState and Logout actions', function () 
    logoutService.logout();

    // how to check the dispactched actions and their parameters?
    // expect(...)
  );

如何检查已分派的操作?

【问题讨论】:

创建一个 store 的 mock 并检查 dispatched 是否使用对应的参数调用了一次。 我还没有在我的 NGXS 测试中尝试过,但是您可以订阅 action stream 以在这些操作被调度时收到通知吗? 我已经完成了@GarthMason 提到的事情,并且有效。谢谢! 【参考方案1】:

NGXS 管道运算符

NGXS 中的动作由 Observables 处理。 NGXS 为您提供 Pipeable Operators,您可以使用ofActionDispatched 进行测试。这是我从NGXS documentation 中获取的列表:

ofAction 在以下任何生命周期事件发生时触发 ofActionDispatched 在调度操作时触发 ofActionSuccessful 动作完成时触发 成功 ofActionCanceled 在取消操作时触发 ofActionErrored 在某个操作导致错误时触发 抛出 ofActionCompleted 动作完成时触发 是否成功(返回完成摘要)

回答

1.创建变量actions$

describe('control-center.state', () => 
  let actions$: Observable<any>;

  // ...
);

2。用 observable 初始化变量 actions$

beforeEach(() => 
  TestBed.configureTestingModule(
    imports: [
      NgxsModule.forRoot([AppState]),
      NgxsModule.forFeature([ControlCenterState])
    ]
  );
  store = TestBed.get(Store);
  actions$ = TestBed.get(Actions);
)

3.1 测试是否调用了 1 个操作:

使用运算符 ofActionsDispatched() 从流中过滤您的操作。

it('should dispatch LogoutAction', (done) => 
  actions$.pipe(ofActionDispatched(LogoutAction)).subscribe((_) => 
    done();
  );

  service.logout();
);

3.2 测试是否调用了多个动作:

使用 RXJS zip 运算符将两个 observables 与 ofActionsDispatched() 函数组合(zip:在 所有 observables 发出之后,将值作为数组发出)。

it('should dispatch ResetStateAction and LogoutAction', (done) => 
  zip(
    actions$.pipe(ofActionDispatched(ResetStateAction)),
    actions$.pipe(ofActionDispatched(LogoutAction))
  ).subscribe((_) => 
    done();
  );

  service.logout();
);

在调用 done 之前,规范不会完成。如果不调用done,则会抛出超时异常。

来自Jasmine documentation。

【讨论】:

我已经测试了这个实现,在 ofActionDispatched 过滤器中有一个小错误,它应该这样调用:ofActionDispatched(ResetStateAction, LogoutAction)。请注意,操作不应包含在列表中。此外,不应需要 done 功能。如果像上面那样实施,测试实际上会错误地通过。以异步方式包装测试按预期工作。 @salomonvh ofActionDispatched 确实需要一个列表,更改了它。关于您的下一条语句,您需要 done 函数,否则它将错误地通过:如果 done 函数通过,则测试期望调用它,如果在 x 秒内未调用它,则测试将失败。【参考方案2】:

我尝试了这种方法来测试是否调用了这两个操作:

3.测试是否正在调用操作

// ...
it('should call actions ResetStateAction and LogoutAction', async( () => 
  let actionDispatched = false;
  zip(
    actions$.pipe(ofActionDispatched(ResetStateAction)),
    actions$.pipe(ofActionDispatched(LogoutAction))
  )
  .subscribe( () => actionDispatched = true );

  store.dispatch([new ResetStateAction(), new LogoutAction()])
    .subscribe(
      () => expect(actionDispatched).toBe(true)
    );
));
// ...

【讨论】:

我不明白你测试的意义。您分派 2 个动作,然后测试它们是否被分派。 @bndamm 我测试中的调度调用只是一个例子。在实际场景中,您将测试一个方法或第三个不同的操作在执行期间是否有效地调用了感兴趣的操作。 @Claudiosuardi 您在谈论 ofActionDispatched,它“似乎等于 ResetStateAction 或 LogoutAction”。这是不正确的,文档:This will ONLY grab actions that have just been dispatched,它只过滤掉你通过的那些。 @Brampage 我明白你在说什么。我更改了答案以避免遗漏不正确的信息。【参考方案3】:

使用茉莉花间谍

我相信在单元测试中,所有相关依赖项的实际实现都应该被模拟,因此我们不应该在此处包含任何实际存储。 在这里,我们为 Store 提供了一个 jasmine spy,并且只是检查是否使用正确的参数调度了某些操作。这也可以用来提供存根数据。

describe('LogoutService', () => 
  let storeSpy: jasmine.SpyObj<Store>;

  beforeEach(() => 
    storeSpy = jasmine.createSpyObj(['dispatch']);

    TestBed.configureTestingModule(
      providers: [LogoutService,  provide: Store, useValue: storeSpy ]
    );
  )

  it('should dispatch Logout and Reset actions', () => 
    storeSpy.dispatch.withArgs([
      jasmine.any(ResetStateAction), 
      jasmine.any(LogoutAction)])
     .and
     .callFake(([resetAction, logoutAction]) => 
       expect(resetAction.payload).toEqual(...something);
       expect(logoutAction.payload).toEqual(...somethingElse);
    );

    TestBed.inject(LogoutService).logout();
);

【讨论】:

以上是关于NGXS:如何测试一个动作是不是被调度?的主要内容,如果未能解决你的问题,请参考以下文章

如果 thunk 动作创建者中的 thunk 动作已被调度,我如何检查一个笑话测试?

Ngxs - 调用 Angular 服务:好的做法?

NGXS调度为状态运行所有操作

ngrx 效果:由一个效果调度的动作是不是立即被其他效果处理?

jest redux-thunk 测试是不是调度了相同模块的操作

如何测试仅调度其他操作的 Redux 操作创建器