How to test an NGRX effect that uses a store selector?

And*_*w B 7 rxjs ngrx ngrx-effects angular

I have an @Effect that uses a MemoizedSelector to grab an item from the redux store and mergeMap it with the payload of an Action. The effect works just fine, however setting up the Jest tests for this has proven difficult as I cannot seem to mock the return value of the selector due to select being a declared function that's imported (from '@ngrx/store') and used in the effect and the selector itself being an imported function as well. I'm grasping at straws now.

How can I write a Unit Test to test an NGRX effect that makes use of a store selector?
"@ngrx/store": "^7.4.0",
"rxjs": "^6.2.2"

I have tried the following kinds of solutions:

  1. using
provideMockStore({
  initialState
})
Run Code Online (Sandbox Code Playgroud)

provideMockStore comes in from '@ngrx/store/testing'; where initial state was both my actual initialState and a state that contains the exact structure/item I'm trying to select

  1. using different types of MockStore's from various SO questions/answers as well as different blog posts approaches

  2. attempting to mock the selector using <selector>.projector(<my-mock-object>) (straw-grasping here, I'm pretty sure this would be used in isolated testing of the selector not the Effect)

The Effect itself:

@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,
    }))
  );
Run Code Online (Sandbox Code Playgroud)

The Spec:

......
  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);
  });
Run Code Online (Sandbox Code Playgroud)

The Selector selectProduct:

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

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

export const selectProduct = createSelector(
  getProductState,
  getProduct,
);
Run Code Online (Sandbox Code Playgroud)

I expect the test to pass but instead I keep getting the following error

? 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}}]
Run Code Online (Sandbox Code Playgroud)

Clearly the MemoizedSelector (selectProduct) doesn't know what the Product Object is that should be in the store (but doesn't seem to be whether I inject an initialState that has it or not) and can't get the product_id of the Product because I didn't set this up correctly in the beforeEach or in the spec itself...

tim*_*ver 5

我们在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 }
  );
Run Code Online (Sandbox Code Playgroud)
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');
    });
  });
Run Code Online (Sandbox Code Playgroud)

确保您的状态具有与 redux 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);
  });
});
Run Code Online (Sandbox Code Playgroud)