我有一个@Effect
,它使用MemoizedSelector
从redux存储中获取一个项目,并mergeMap
与一个Action的有效负载一起使用。效果很好,但是为此设置了Jest测试已被证明很困难,因为select
是导入的声明函数(从'@ ngrx导入),我似乎无法模拟选择器的返回值/ store'),并在效果中使用,选择器本身也是导入功能。我现在正在抓稻草。
如何编写单元测试以测试使用商店选择器的NGRX效果?
“ @ ngrx /商店”:“ ^ 7.4.0” ,
“ rxjs”:“ ^ 6.2.2”
provideMockStore({
initialState
})
provideMockStore 来自'@ngrx/store/testing';
,其中初始状态既是我的 actual initialState,又是包含我要选择的确切结构/项目的状态
使用来自各种SO问题/答案的不同类型的MockStore
和不同的博客帖子方法
试图使用<selector>.projector(<my-mock-object>)
来模拟选择器(在这里抓草,我很确定这将用于 selector 的隔离测试中,而不是效果上)< / p>
效果本身:
@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)不知道商店中应该有什么产品对象(但似乎不是我是否注入了具有该对象的initialState
) ),并且无法获得产品的product_id
,因为我没有在beforeEach
或规范本身中正确设置此产品...
答案 0 :(得分:1)
我们在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');
});
});
确保您的状态与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);
});
});