Как протестировать несколько инъекций withLatestFrom store в NgRx-Effects?

#angular #typescript #unit-testing #testing #ngrx

#angular #typescript #модульное тестирование #тестирование #ngrx

Вопрос:

В нашем веб-приложении angular есть несколько NgRx-эффектов, которые используют разную информацию из разных частей нашего магазина. Для этого мы используем рекомендуемый withLatestFrom -подход:

 withLatestFrom(
   this.store.pipe(select(...)),
   this.store.pipe(select(...)),
   ...
)
  

Хотя этот подход, похоже, отлично работает в рабочей среде, он кажется ужасным для модульного тестирования эффектов.

В настоящее время для наших модульных тестов мы используем jasmine-marbles, объекты-шпионы jasmine и макет ngrx (NgRx 7 ). Самое сложное — обеспечить необходимое состояние хранилища, чтобы селекторы могли работать должным образом.

ПРИМЕР-ЭФФЕКТ, для которого у нас нет модульного теста:

 @Effect()
getStammdatenDetails$: Observable<Action> = this.actions$.pipe(
   ofType(StammdatenItemDetailActionTypes.REQUEST_DETAILS),
   withLatestFrom(
      this.store.pipe(select(fromRoot.getMetadata)),
      this.store.pipe(select(fromRoot.getCustomerId)),
      this.store.pipe(select(fromRoot.getRouterParams))
   ),
   mergeMap(([action, metadata, customerId, params]) => {
      *effect logic*
   })
);
  

Может быть, кто-нибудь здесь может предоставить больше информации или полезную ссылку на часть документации, которой нам не хватает?

Я был бы очень признателен за любую помощь в отношении того, существует ли удобный метод модульного тестирования таких эффектов или как реорганизовать эти эффекты, чтобы они были более «тестируемыми» (без переноса проблемы на другой фрагмент кода, который мы не сможем протестировать впоследствии).

Комментарии:

1. Это ваши первые шаги в тестировании эффектов, или вы уже пробовали некоторые шаблоны раньше?

2. Мы уже настроили несколько тестов для наших менее сложных эффектов, но, честно говоря, я не совсем уверен, о каких шаблонах вы говорите. Не хотите поделиться хорошим чтением?

3. Конечно, дайте мне 1 час, я что-нибудь найду

4. Пожалуйста, скажите мне, вы использовали такие материалы, как TestBet, Spy и асинхронный модульный тест в Karma?

5. Мы настроили наш тестовый стенд с необходимыми Spys (для используемых сервисов) и используем среду fakeAsync() для тестов, которые в ней нуждаются.

Ответ №1:

Я предлагаю сделать что-то вроде этого:

 describe('ChapterEffects', () => {
  const actions$ = new Subject<any>();
  let effects: ChapterEffects;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        TestedEffects,
        provideMockActions(() => actions$),
        ...otherMocks,
      ],
    });

    effects = TestBed.get(ChapterEffects);
  });

  it('', () => {

  });
});
  

Протестированный эффект:

   @Effect()
  fetchData$ = this.actions$.pipe(
    ofType<FetchAction>(ActionTypes.FetchAction),
    switchMap(({ payload }) => {
      return this.someService
        .get(payload)
        .pipe(
          map((data) => new LoadAction(data)),
          catchError(() => new ErrorAction()),
        );
    }),
  );
  

Вот как выглядит ваш эффект и тест. Вы хотите проверить, FetchAction будет ли вызываться запрос get и загружаться LoadAction , когда запрос завершится.

Первое, что нужно сделать, это смоделировать SomeService в testBet:

 const otherMocks = [ { provide: SomeService, useValue: { get: () => of(42)} }, ]
  

теперь в вашей тестовой среде выполнения this.SomeService будет { get: () => of(42)} } .

Теперь давайте смоделируем FetchAction. В test actions$ есть Subject, который вы можете вызвать actions$.next(new FetchRequest(7)) (7 — это полезная нагрузка).

Тогда эффект out должен выдавать LoadAction с 42, так что:

 it('', (done) => {
  effect.fetchData$.subscribe(action => {
    expect(action.payload).toEqual(42);
  }
  done()
  actions$.next(7);
});
  

Комментарии:

1. Хотя это хорошее краткое изложение того, как тестировать эффекты ngrx в целом, часть, с которой я борюсь в своем вопросе, заключается в том, как удобно тестировать эффекты, которые используют части хранилища (следовательно withLatestFrom(this.store.pipe(select(...))) ). Одним из способов было бы полностью смоделировать хранилище, но поскольку наша структура хранилища уже довольно большая, я чувствую, что должно быть более удобное решение.

2. Это будет практически тот же случай, что и в SomeService . this.store берется из DI, поэтому вы можете легко смоделировать его с помощью { provide: Store, useValue: new BehaviorSubject() } , а с помощью BehaviorSubject вы можете указать текущее смоделированное состояние.

3. Как уже говорилось, я уже знал об этой опции (но я бы предпочел использовать MockStore из ngrx). Структура нашего хранилища глубоко вложена, так что создание такого хранилища для разных тестовых наборов требует больших затрат. Но, возможно, другого способа в настоящее время нет… Я приму этот ответ, спасибо за вашу помощь, это высоко ценится!

4. Я знаю это, hoverer, я не хотел рекомендовать вам шаблоны, которые я не проверял в своих приложениях. Для меня это не идеально, потому что требует много кода, но работает хорошо.

5. Из чистого интереса: не могли бы вы назвать те шаблоны, которые вы не можете рекомендовать? Я хотел бы прочитать о них!