【问题标题】:How to correctly test effects in ngrx 4?如何正确测试ngrx 4中的效果?
【发布时间】:2017-10-17 13:54:54
【问题描述】:

ngrx 3中有很多关于如何测试效果的教程。

但是,我发现 ngrx4 只有 1 或 2 个(他们通过 EffectsTestingModule 删除了经典方法),例如the official tutorial

但是,就我而言,他们的方法不起作用。

effects.spec.ts(在下面链接中的 src/modules/list/store/list 下)

 describe('addItem$', () => {
    it('should return LoadItemsSuccess action for each item', async() => {
      const item = makeItem(Faker.random.word);
      actions = hot('--a-', { a: new AddItem({ item })});

      const expected = cold('--b', { b: new AddUpdateItemSuccess({ item }) });
      // comparing marbles
      expect(effects.addItem$).toBeObservable(expected);
    });
  })

effects.ts(在下面链接中的 src/modules/list/store/list 下)

...
 @Effect() addItem$ = this._actions$
    .ofType(ADD_ITEM)
    .map<AddItem, {item: Item}>(action => {
      return action.payload
    })
    .mergeMap<{item: Item}, Observable<Item>>(payload => {
      return Observable.fromPromise(this._listService.add(payload.item))
    })
    .map<any, AddUpdateItemSuccess>(item => {
      return new AddUpdateItemSuccess({
        item,
      })
    });
...

错误

 should return LoadItemsSuccess action for each item
        Expected $.length = 0 to equal 1.
        Expected $[0] = undefined to equal Object({ frame: 20, notification: Notification({ kind: 'N', value: AddUpdateItemSuccess({ payload: Object({ item: Object({ title: Function }) }), type: 'ADD_UPDATE_ITEM_SUCCESS' }), error: undefined, hasValue: true }) }).
            at compare (webpack:///node_modules/jasmine-marbles/index.js:82:0 <- karma-test-shim.js:159059:33)
            at Object.<anonymous> (webpack:///src/modules/list/store/list/effects.spec.ts:58:31 <- karma-test-shim.js:131230:42)
            at step (karma-test-shim.js:131170:23)

注意:效果使用了一个涉及写入 PouchDB 的服务。但是,这个问题似乎与此无关 并且效果在运行的应用程序中起作用

完整代码是 Ionic 3 应用程序,可在 here 找到(只需克隆、npm i 和 npm run test

更新

使用 ReplaySubject 可以,但不能使用热/冷弹珠

  const item = makeItem(Faker.random.word);
  actions = new ReplaySubject(1) // = Observable + Observer, 1 = buffer size

  actions.next(new AddItem({ item }));

  effects.addItem$.subscribe(result => {
    expect(result).toEqual(new AddUpdateItemSuccess({ item }));
  });

【问题讨论】:

    标签: testing ngrx-effects ngrx-store-4.0


    【解决方案1】:

    @phillipzada 在Github issue I posted. 回答了我的问题

    对于稍后查看此内容的任何人,我在这里报告答案:

    在使用弹珠使用 Promise 时,这似乎是一个 RxJS 问题。 https://stackoverflow.com/a/46313743/4148561

    我确实设法做了一些应该可以工作的 hack,但是,您需要对正在调用的服务进行单独的测试,除非您可以更新服务以返回 observable 而不是 promise。

    基本上我所做的是将 Observable.fromPromise 调用提取到它自己的“内部函数”中,我们可以模拟它来模拟对服务的调用,然后从那里查看。

    这样你就可以在不使用弹珠的情况下测试内部函数_addItem。

    效果

    import 'rxjs/add/observable/fromPromise';
    import 'rxjs/add/operator/map';
    import 'rxjs/add/operator/mergeMap';
    
    import { Injectable } from '@angular/core';
    import { Actions, Effect } from '@ngrx/effects';
    import { Action } from '@ngrx/store';
    import { Observable } from 'rxjs/Observable';
    
    export const ADD_ITEM = 'Add Item';
    export const ADD_UPDATE_ITEM_SUCCESS = 'Add Item Success';
    
    export class AddItem implements Action {
        type: string = ADD_ITEM;
        constructor(public payload: { item: any }) { }
    }
    
    export class AddUpdateItemSuccess implements Action {
        type: string = ADD_UPDATE_ITEM_SUCCESS;
        constructor(public payload: { item: any }) { }
    }
    
    export class Item {
    
    }
    
    export class ListingService {
        add(item: Item) {
            return new Promise((resolve, reject) => { resolve(item); });
        }
    }
    
    @Injectable()
    export class SutEffect {
    
        _addItem(payload: { item: Item }) {
            return Observable.fromPromise(this._listService.add(payload.item));
    
        }
    
        @Effect() addItem$ = this._actions$
            .ofType<AddItem>(ADD_ITEM)
            .map(action => action.payload)
            .mergeMap<{ item: Item }, Observable<Item>>(payload => {
                return this._addItem(payload).map(item => new AddUpdateItemSuccess({
                    item,
                }));
            });
    
        constructor(
            private _actions$: Actions,
            private _listService: ListingService) {
    
        }
    }
    

    规格

    import { cold, hot, getTestScheduler } from 'jasmine-marbles';
    import { async, TestBed } from '@angular/core/testing';
    import { Actions } from '@ngrx/effects';
    import { Store, StoreModule } from '@ngrx/store';
    import { getTestActions, TestActions } from 'app/tests/sut.helpers';
    
    import { AddItem, AddUpdateItemSuccess, ListingService, SutEffect } from './sut.effect';
    import { Observable } from 'rxjs/Observable';
    
    import 'rxjs/add/observable/of';
    
    describe('Effect Tests', () => {
    
        let store: Store<any>;
        let storeSpy: jasmine.Spy;
    
        beforeEach(async(() => {
    
            TestBed.configureTestingModule({
                imports: [
                    StoreModule.forRoot({})
                ],
                providers: [
                    SutEffect,
                    {
                        provide: ListingService,
                        useValue: jasmine.createSpyObj('ListingService', ['add'])
                    },
                    {
                        provide: Actions,
                        useFactory: getTestActions
                    }
                ]
            });
    
            store = TestBed.get(Store);
            storeSpy = spyOn(store, 'dispatch').and.callThrough();
            storeSpy = spyOn(store, 'select').and.callThrough();
    
        }));
    
        function setup() {
            return {
                effects: TestBed.get(SutEffect) as SutEffect,
                listingService: TestBed.get(ListingService) as jasmine.SpyObj<ListingService>,
                actions$: TestBed.get(Actions) as TestActions
            };
        }
    
        fdescribe('addItem$', () => {
            it('should return LoadItemsSuccess action for each item', async () => {
    
                const { effects, listingService, actions$ } = setup();
                const action = new AddItem({ item: 'test' });
                const completion = new AddUpdateItemSuccess({ item: 'test' });
    
                // mock this function which we can test later on, due to the promise issue
                spyOn(effects, '_addItem').and.returnValue(Observable.of('test'));
    
                actions$.stream = hot('-a|', { a: action });
                const expected = cold('-b|', { b: completion });
    
                expect(effects.addItem$).toBeObservable(expected);
                expect(effects._addItem).toHaveBeenCalled();
    
            });
        })
    
    })
    

    帮助者

    import { Actions } from '@ngrx/effects';
    import { Observable } from 'rxjs/Observable';
    import { empty } from 'rxjs/observable/empty';
    
    export class TestActions extends Actions {
        constructor() {
            super(empty());
        }
        set stream(source: Observable<any>) {
            this.source = source;
        }
    }
    
    export function getTestActions() {
        return new TestActions();
    }
    

    【讨论】:

      猜你喜欢
      • 2018-01-04
      • 2018-10-05
      • 1970-01-01
      • 2019-06-22
      • 1970-01-01
      • 1970-01-01
      • 2022-01-15
      • 2018-06-16
      • 1970-01-01
      相关资源
      最近更新 更多