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:
- using
provideMockStore({
initialState
})
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
using different types of
MockStore
's from various SO questions/answers as well as different blog posts approachesattempting 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,
}))
);
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);
});
The Selector selectProduct
:
export const getProduct = ({product}: fromProducts.State) => product;
export const getProductState = createFeatureSelector<
fromProducts.State
>('product');
export const selectProduct = createSelector(
getProductState,
getProduct,
);
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}}]
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...