【问题标题】:How to test an NGRX effect that uses a store selector?如何测试使用商店选择器的 NGRX 效果?
【发布时间】:2019-09-30 21:39:45
【问题描述】:

我有一个@Effect,它使用MemoizedSelector 从redux 存储中获取一个项目,并使用mergeMap 它和一个Action 的有效负载。效果很好,但是为此设置 Jest 测试证明很困难,因为我似乎无法模拟选择器的返回值,因为 select 是一个已声明的导入函数(来自 '@ngrx/store ') 并在效果中使用,并且选择器本身也是一个导入函数。我现在正抓着稻草。

如何编写单元测试来测试使用商店选择器的 NGRX 效果?
"@ngrx/store": "^7.4.0",
"rxjs": "^6.2.2"

我尝试了以下几种解决方案:

  1. 使用
provideMockStore({
  initialState
})

provideMockStore 来自'@ngrx/store/testing';,其中初始状态既是我的实际 initialState,也是包含我要选择的确切结构/项目的状态

  1. 使用来自各种 SO 问题/答案的不同类型的 MockStore 以及不同的博客文章方法

  2. 尝试使用<selector>.projector(<my-mock-object>) 模拟选择器(这里是草根,我很确定这将用于选择器而不是效果的隔离测试)

效果本身:

@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 或规范本身中正确设置它...

【问题讨论】:

    标签: angular rxjs ngrx ngrx-effects


    【解决方案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');
        });
      });
    

    确保您的 state 与 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);
      });
    });
    

    【讨论】:

    • aaaahhh,不知何故,我没有想到在文档中查找测试版!我一直在关注当前的稳定版或查看以前的版本。
    • 非常感谢您的帮助。我很抱歉错过了 beta 文档。我没有正确模拟 initialState! 中的状态
    猜你喜欢
    • 1970-01-01
    • 2018-03-25
    • 2018-05-11
    • 1970-01-01
    • 1970-01-01
    • 2018-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多