NgRx选择器在效果单元测试中不起作用

时间:2019-07-25 10:48:51

标签: angular ngrx ngrx-store ngrx-effects angular-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;
}

2 个答案:

答案 0 :(得分:1)

尝试覆盖选择器:

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}});

答案 1 :(得分:1)

您可以使用类来模拟选择器。

让商店:商店

class MockStore {
  select(){}
}

TestBed.configureTestingModule({
 providers: [
   {
     provide: Store,
     useClass: MockStore
   }
 ]
});
store = TestBed.get(Store);

在测试套件中,您可以使用Spy为您提供所需的任何商店:

spyOn(store, 'select').and.returnValue(of(initialState));