Как проверить эффект NGRX, который использует селектор магазина? - PullRequest
4 голосов
/ 14 мая 2019

У меня есть @Effect, который использует MemoizedSelector, чтобы взять предмет из магазина с избыточностью и mergeMap с полезной нагрузкой действия.Эффект работает просто отлично, однако настройка Jest-тестов для этого оказалась трудной, поскольку я не могу посмеяться над возвращаемым значением селектора, поскольку select является объявленной функцией, которая импортируется (из '@ ngrx / store') и используется в эффекте, а сам селектор также является импортированной функцией.Теперь я цепляюсь за соломинку.

Как мне написать модульный тест для проверки эффекта NGRX, использующего селектор магазина?
"@ ngrx / store": "^ 7.4.0 ",
" rxjs ":" ^ 6.2.2 "

Я пробовал следующие виды решений:

  1. использование
provideMockStore({
  initialState
})

provideMockStore происходит из '@ngrx/store/testing';, где начальным состоянием было и мое фактическое initialState, и состояние, которое содержит точную структуру / элементЯ пытаюсь выбрать

с использованием различных типов MockStore из различных SO вопросов / ответов, а также различных подходов к публикациям в блогах

попытка издеваться над селекторомиспользуя <selector>.projector(<my-mock-object>) (здесь мы понимаем, что это так, и я уверен, что это будет использовано при изолированном тестировании селектора , а не эффекта)

самого эффекта:

@Effect()
  getReviewsSuccess$ = this.actions$.pipe(
    ofType<ProductActions.GetReviewsSuccess>(
      ProductActions.ProductActionTypes.GET_REVIEWS_SUCCESS
    ),
    mergeMap(() => this.reduxStore.pipe(select(selectProduct))),
    map(({ product_id }) => product_id),
    map(product_id => new ProductActions.GetReviewsMeta({
      product_id,
    }))
  );

Спецификация:

......
  let effects: ProductEffects;
  let facade: Facade;
  let actions$: Observable<any>;
  let store$: Observable<State>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule,
        // ^ I've also tried using StoreModule.forRoot(...) here to configure 
        // it in similar fashion to the module where this effect lives
      ],
      providers: [
        ProductEffects,
        provideMockActions(() => actions$),
        {
          provide: Facade,
          useValue: facadeServiceMock,
        },
        ResponseService,
        provideMockStore({
          initialState
        })
        // ^ also tried setting up the test with different variations of initialState
      ],
    });
......

it('should return a GetReviewsMeta on successful GetReviewsSuccess', () => {
    const reviews = {...reviewListMock};
    const { product_id } = {...productMockFull};

    const action = new ProductActions.GetReviewsSuccess({
      reviews
    });

    const outcome = new ProductActions.GetReviewsMeta({
      product_id
    });

    actions$ = hot('-a', { a: action }); 

    // store$ = cold('-c', { c: product_id });  
    // not sure what, if anything I need to do here to mock select(selectProduct)  

    const expected = cold('-b', { b: outcome });  
    expect(effects.getReviewsSuccess$).toBeObservable(expected);
  });

Селектор selectProduct:

export const getProduct = ({product}: fromProducts.State) => product;

export const getProductState = createFeatureSelector<
    fromProducts.State
>('product');

export const selectProduct = createSelector(
  getProductState,
  getProduct,
);

Я ожидаю, что тест пройден, но вместо этого я получаю следующееошибка

● Product Effects › should return a GetReviewsMeta on successful GetReviewsSuccess

    expect(received).toBeNotifications(expected)

    Expected notifications to be:
      [{"frame": 10, "notification": {"error": undefined, "hasValue": true, "kind": "N", "value": {"payload": {"product_id": 2521}, "type": "[Reviews] Get Reviews Meta"}}}]
    But got:
      [{"frame": 10, "notification": {"error": [TypeError: Cannot read property 'product_id' of undefined], "hasValue": false, "kind": "E", "value": undefined}}]

Ясно, что MemoizedSelector (selectProduct) не знает, что такое объект Product, который должен находиться в магазине (но, похоже, не вводит ли я initialState, который имеетэто или нет), и я не могу получить product_id Продукта, потому что я неправильно настроил его в beforeEach или в самой спецификации ...

1 Ответ

2 голосов
/ 14 мая 2019

Мы получили это в ngrx.io документах.Обратите внимание, что синтаксис для NgRx 8, но те же идеи учитываются для NgRx 7.

addBookToCollectionSuccess$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(CollectionApiActions.addBookSuccess),
        withLatestFrom(this.store.pipe(select(fromBooks.getCollectionBookIds))),
        tap(([, bookCollection]) => {
          if (bookCollection.length === 1) {
            window.alert('Congrats on adding your first book!');
          } else {
            window.alert('You have added book number ' + bookCollection.length);
          }
        })
      ),
    { dispatch: false }
  );
it('should alert number of books after adding the second book', () => {
      store.setState({
        books: {
          collection: {
            loaded: true,
            loading: false,
            ids: ['1', '2'],
          },
        },
      } as fromBooks.State);

      const action = CollectionApiActions.addBookSuccess({ book: book1 });
      const expected = cold('-c', { c: action });
      actions$ = hot('-a', { a: action });
      expect(effects.addBookToCollectionSuccess$).toBeObservable(expected);
      expect(window.alert).toHaveBeenCalledWith('You have added book number 2');
    });
  });

Убедитесь, что ваше состояние имеет ту же структуру, что и в devtools-редуктах.

NgRx 8 также предоставляет способ имитации селекторов, поэтому нет необходимости настраивать все дерево состояний для одного теста - https://next.ngrx.io/guide/store/testing#using-mock-selectors.

describe('Auth Guard', () => {
  let guard: AuthGuard;
  let store: MockStore<fromAuth.State>;
  let loggedIn: MemoizedSelector<fromAuth.State, boolean>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [AuthGuard, provideMockStore()],
    });

    store = TestBed.get(Store);
    guard = TestBed.get(AuthGuard);

    loggedIn = store.overrideSelector(fromAuth.getLoggedIn, false);
  });

  it('should return false if the user state is not logged in', () => {
    const expected = cold('(a|)', { a: false });

    expect(guard.canActivate()).toBeObservable(expected);
  });

  it('should return true if the user state is logged in', () => {
    const expected = cold('(a|)', { a: true });

    loggedIn.setResult(true);

    expect(guard.canActivate()).toBeObservable(expected);
  });
});
...