NGXS: Как проверить, было ли отправлено действие?

Как выполнить модульное тестирование, было ли отправлено действие?

Например, в 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(...)
  });

Как я могу проверить отправленные действия?


person Francesco Cina    schedule 28.06.2018    source источник
comment
создайте имитацию магазина и проверьте, вызывалась ли отправка один раз с соответствующими аргументами.   -  person René Winkler    schedule 28.06.2018
comment
Я еще не пробовал это в своем тестировании с NGXS, но не могли бы вы подписаться на действие stream, чтобы получать уведомления об этих действиях?   -  person Garth Mason    schedule 03.07.2018
comment
Я сделал то, что упомянул @GarthMason, и это работает. Спасибо!   -  person Uchitha    schedule 11.04.2019


Ответы (3)


Операторы конвейерной передачи NGXS

Действия в NGXS обрабатываются с помощью Observables. NGXS предоставляет вам конвейерные операторы, для вашего теста вы можете использовать ofActionDispatched. Вот список, который я взял из документации NGXS:

  • ofAction срабатывает, когда происходит любое из перечисленных ниже событий жизненного цикла.
  • ofActionDispatched срабатывает при отправке действия
  • ofActionSuccessful срабатывает при успешном завершении действия
  • ofActionCanceled срабатывает, когда действие было отменено
  • ofActionErrored срабатывает, когда действие вызвало ошибку
  • ofActionCompleted срабатывает, когда действие было завершено, независимо от того, было оно успешным или нет (возвращает сводку о завершении)

Отвечать

1. Создать переменную actions$

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

  // ...
});

2. Инициализировать переменную 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 Проверьте, было ли вызвано несколько действий:

Используйте оператор zip RXJS, чтобы объединить два наблюдаемых объекта с функцией ofActionsDispatched() (zip: после того, как все наблюдаемые объекты испускают, испускают значения в виде массива).

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

  service.logout();
});

Спецификация не будет завершена, пока не будет вызвана ее done. Если done не вызывается, будет сгенерировано исключение тайм-аута.

Из документации Jasmine.

person Brampage    schedule 02.08.2018
comment
Я протестировал эту реализацию и обнаружил небольшую ошибку в фильтре ofActionDispatched, его следует называть так: ofActionDispatched (ResetStateAction, LogoutAction). Обратите внимание, что действия не следует заключать в список. Кроме того, функция done не требуется. Если реализовано, как указано выше, тест на самом деле будет ошибочным. Обертывание теста в асинхронном режиме работает должным образом. - person salomonvh; 30.11.2018
comment
@salomonvh ofActionDispatched действительно берет список, изменил его. Что касается вашего следующего оператора, вам нужна функция done, иначе она будет ложно пройдена: если функция done будет передана, тест ожидает, что она будет вызвана, если она не будет вызвана в течение x секунд, тест завершится ошибкой. - person Brampage; 16.12.2019

Я попробовал этот подход, чтобы проверить, были ли вызваны оба действия:

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)
    );
}));
// ...
person Claudio Suardi    schedule 22.02.2019
comment
Я не понимаю значения вашего теста. Вы отправляете 2 действия, чтобы затем проверить, были ли они отправлены. - person bndamm; 12.11.2019
comment
@bndamm вызовы диспетчеризации в моем тесте - всего лишь пример. В реальном сценарии вы должны проверить, что метод или третье другое действие эффективно вызывает интересующие действия во время выполнения. - person Claudio Suardi; 13.11.2019
comment
@ClaudioSuardi Вы говорите о ofActionDispatched, который кажется равным ResetStateAction ИЛИ LogoutAction. Это неверно, документация: This will ONLY grab actions that have just been dispatched, отфильтровываются только те, которые вы прошли. - person Brampage; 10.12.2019
comment
@Brampage Я понимаю, о чем ты говоришь. Я изменил свой ответ, чтобы не пропустить неверную информацию. - person Claudio Suardi; 10.12.2019

Использование Jasmine Spies

Я считаю, что при модульном тестировании необходимо высмеивать фактическую реализацию всех связанных зависимостей, и, следовательно, мы не должны включать сюда какие-либо фактические хранилища. Здесь мы предоставляем jasmine spy для Store и просто проверяем, отправляются ли определенные действия с правильными параметрами. Это также может быть использовано для предоставления данных-заглушек.

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();
});
person Ankit Kaushik    schedule 09.11.2020