NgRx 选择器在效果单元测试中不起作用
NgRx Selectors not Working in Effects Unit Test
我正在为我的 NgRx 效果编写单元测试(资源:https://ngrx.io/guide/effects/testing)。但我对 withLatestFrom
有疑问。通过选择器选择我的状态的一部分时(例如:this.store.select( mySelectors.selectId )
),测试会抛出错误:
zone.js:199 Uncaught TypeError: Cannot read property 'id' of undefined
at :9876/_karma_webpack_/webpack:/src/app/root-store/my-store/my.selectors.ts:13
at :9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:504
at memoized (:9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:454)
at defaultStateFn (:9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:473)
at :9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:507
at memoized (:9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:446)
at MapSubscriber.project (:9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:401)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm5/internal/operators/map.js:35)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm5/internal/Subscriber.js:53)
at MockState.push../node_modules/rxjs/_esm5/internal/BehaviorSubject.js.BehaviorSubject._subscribe (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm5/internal/BehaviorSubject.js:22)
但是当我没有使用自己的选择器而是使用类似这样的东西时:this.store.select( state => state.id )
,测试通过。
效果
myEffect$ = createEffect( () =>
this.actions$.pipe(
ofType( myActions.myAction ),
withLatestFrom(
// this doesn't work
this.store.select( mySelectors.selectId ),
// this works
// this.store.select( state => state.id ),
),
switchMap( ( [action, id] ) => { /* ... */ } )
)
);
选择器
export const selectMyState = createFeatureSelector<MyState>( 'feature' );
export const selectId = createSelector(
selectMyState,
( state: MyState ) => state.id
);
测试
describe( 'My Effects', () => {
let effects: MyEffects;
let actions: ReplaySubject<any>;
let store: MockStore<MyState>;
beforeEach( () => {
TestBed.configureTestingModule( {
imports: [HttpClientModule, RouterTestingModule],
providers: [
MyEffects,
provideMockActions( () => actions ),
provideMockStore( { initialState: fromMy.initialState } ),
MyService,
]
} );
TestBed.overrideProvider( MyService, { useValue: new MyMockService() } );
effects = TestBed.get<MyEffects>( MyEffects );
store = TestBed.get<Store<RootStoreState.State>>( Store );
} );
it( '#myEffect$ should work', () => {
actions = new ReplaySubject( 1 );
store.setState( { /* ... */ } );
actions.next( myActions.myAction() );
effects.saveEditMy$.subscribe( result => {
expect( result ).toEqual( myActions.myActionDone( { /* ... */ } ) );
} );
} );
} );
MyState 接口
export interface MyState extends EntityState<MyEntity> {
loading: boolean;
loaded: boolean;
error: string;
buttonStates: string[];
id: string;
edit: boolean;
formData: Partial<MyEntity>;
isFormValid: boolean;
isDeletePopupVisible: boolean;
}
尝试覆盖选择器:
provideMockStore({
initialState: fromMy.initialState,
selectors: [
{
selector: mySelectors.selectId,
value: 1 // your id
}
]
})
编辑 1:
更新不同测试用例中的选择器值:
store.overrideSelector(mySelectors.selectId, 3); // your id
编辑 2:
这里的情况不同,mySelectors.selectId
中的状态是 undefined
因为它来自 feature
的特征选择器所以 setState
应该这样调用:
store.setState({feature:{id: 2}});
您可以使用 class 模拟选择器。
让商店:商店
class MockStore {
select(){}
}
TestBed.configureTestingModule({
providers: [
{
provide: Store,
useClass: MockStore
}
]
});
store = TestBed.get(Store);
并且在测试套件中,您可以使用 Spy 为您提供您想要的任何商店切片:
spyOn(store, 'select').and.returnValue(of(initialState));
我正在为我的 NgRx 效果编写单元测试(资源:https://ngrx.io/guide/effects/testing)。但我对 withLatestFrom
有疑问。通过选择器选择我的状态的一部分时(例如:this.store.select( mySelectors.selectId )
),测试会抛出错误:
zone.js:199 Uncaught TypeError: Cannot read property 'id' of undefined
at :9876/_karma_webpack_/webpack:/src/app/root-store/my-store/my.selectors.ts:13
at :9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:504
at memoized (:9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:454)
at defaultStateFn (:9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:473)
at :9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:507
at memoized (:9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:446)
at MapSubscriber.project (:9876/_karma_webpack_/webpack:/node_modules/@ngrx/store/fesm5/store.js:401)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm5/internal/operators/map.js:35)
at MapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm5/internal/Subscriber.js:53)
at MockState.push../node_modules/rxjs/_esm5/internal/BehaviorSubject.js.BehaviorSubject._subscribe (:9876/_karma_webpack_/webpack:/node_modules/rxjs/_esm5/internal/BehaviorSubject.js:22)
但是当我没有使用自己的选择器而是使用类似这样的东西时:this.store.select( state => state.id )
,测试通过。
效果
myEffect$ = createEffect( () =>
this.actions$.pipe(
ofType( myActions.myAction ),
withLatestFrom(
// this doesn't work
this.store.select( mySelectors.selectId ),
// this works
// this.store.select( state => state.id ),
),
switchMap( ( [action, id] ) => { /* ... */ } )
)
);
选择器
export const selectMyState = createFeatureSelector<MyState>( 'feature' );
export const selectId = createSelector(
selectMyState,
( state: MyState ) => state.id
);
测试
describe( 'My Effects', () => {
let effects: MyEffects;
let actions: ReplaySubject<any>;
let store: MockStore<MyState>;
beforeEach( () => {
TestBed.configureTestingModule( {
imports: [HttpClientModule, RouterTestingModule],
providers: [
MyEffects,
provideMockActions( () => actions ),
provideMockStore( { initialState: fromMy.initialState } ),
MyService,
]
} );
TestBed.overrideProvider( MyService, { useValue: new MyMockService() } );
effects = TestBed.get<MyEffects>( MyEffects );
store = TestBed.get<Store<RootStoreState.State>>( Store );
} );
it( '#myEffect$ should work', () => {
actions = new ReplaySubject( 1 );
store.setState( { /* ... */ } );
actions.next( myActions.myAction() );
effects.saveEditMy$.subscribe( result => {
expect( result ).toEqual( myActions.myActionDone( { /* ... */ } ) );
} );
} );
} );
MyState 接口
export interface MyState extends EntityState<MyEntity> {
loading: boolean;
loaded: boolean;
error: string;
buttonStates: string[];
id: string;
edit: boolean;
formData: Partial<MyEntity>;
isFormValid: boolean;
isDeletePopupVisible: boolean;
}
尝试覆盖选择器:
provideMockStore({
initialState: fromMy.initialState,
selectors: [
{
selector: mySelectors.selectId,
value: 1 // your id
}
]
})
编辑 1:
更新不同测试用例中的选择器值:
store.overrideSelector(mySelectors.selectId, 3); // your id
编辑 2:
这里的情况不同,mySelectors.selectId
中的状态是 undefined
因为它来自 feature
的特征选择器所以 setState
应该这样调用:
store.setState({feature:{id: 2}});
您可以使用 class 模拟选择器。
让商店:商店
class MockStore {
select(){}
}
TestBed.configureTestingModule({
providers: [
{
provide: Store,
useClass: MockStore
}
]
});
store = TestBed.get(Store);
并且在测试套件中,您可以使用 Spy 为您提供您想要的任何商店切片:
spyOn(store, 'select').and.returnValue(of(initialState));