代码之家  ›  专栏  ›  技术社区  ›  TobiDevloft

如何测试离子3中的ngrx/效应?

  •  0
  • TobiDevloft  · 技术社区  · 8 年前

    我正在试着写一些关于我在爱奥尼亚的ngrx效果的测试,但没能成功。网上有几个教程,但它们要么不适用于我的案例,要么不适用于depcreated。中的示例 official ngrx migration guide e、 g.不再工作。

    我想测试这样一种情况,其中LoadAllAction由LoadAllAction$effect调度,然后最终调度LoadAllCompleteAction。

    这是我的密码。我重命名了类和服务以使其更加清晰,因此如果有拼写错误,请忽略它。导入在实际代码中也是正确的。

    我的影响。影响。ts

    import {Injectable} from "@angular/core";
    import {Actions, Effect} from "@ngrx/effects";
    import {catchError, concat, map, switchMap} from "rxjs/operators";
    import {Observable} from "rxjs/Observable";
    import {IMyObject} from "../models/myObject.model";
    import {MyObjectService} from "../../services/myObject.service";
    import {
      InitMyObjectsCompleteAction,
      LOAD_ALL,
      LOAD_ALL_COMPLETE,
      LoadAllAction,
      LoadAllCompleteAction,
      SaveMyObjectsCompleteAction, MyObjectsErrorAction
    } from "../actions/object.actions";
    import {defer} from "rxjs/observable/defer";
    import {LogService} from "../../services/log/log.service";
    import {ToastService} from "../../services/toast.service";
    
    @Injectable()
    export class MyEffects {
    
      constructor(private action$: Actions,
                  private myObjectService: MyObjectService,
                  private logService: LogService,
                  private toastService: ToastService) {
      }
    
      @Effect() init$ = defer(() => Observable.fromPromise(this.myObjectService.getSavedMyObjects())
        .pipe(
          map((favoredObjects: IMyObject[]) => new InitMyObjectsCompleteAction(favoredObjects)),
          catchError((error) => this.createErrorObservableAndLog(error)),
          concat(Observable.of(new LoadAllAction()))
        ));
    
      @Effect() loadAllAction$ = this.action$.ofType(LOAD_ALL).pipe(
        switchMap(() => this.myObjectService.loadObjects().pipe(
          map((favoredObjects: IMyObject[]) => new LoadAllCompleteAction(favoredObjects)),
          catchError((error) => {
            this.toastService.couldNotUpdateObject();
            return this.createErrorObservableAndLog(error)
          })
        ))
      );
    
      @Effect() loadAllCompleteAction$ = this.action$.ofType(LOAD_ALL_COMPLETE).pipe(
        switchMap((action: LoadAllCompleteAction) => Observable.fromPromise(this.myObjectService.saveObjects(action.favoredObjects))
          .pipe(
            map(() => new SaveMyObjectsCompleteAction()),
            catchError((error) => this.createErrorObservableAndLog(error))
          )
        )
      );
    
      private createErrorObservableAndLog(error){
        this.logService.error(error);
        return Observable.of(new MyObjectsErrorAction(error))
      }
    }
    

    模拟。ts

    export class MyObjectsServiceMock {
    
      loadMyObjects(): Observable<MyObjects[]> {
        return createDummyMyObjectsListObservable();
      }
    }
    
    export class StorageMock {
      get() {
        return [];
      }
    
      set() {
    
      }
    }
    export class StoreMock {
    
      private returnMyObjects: boolean = false;
    
      public dispatch() {
        return Observable.empty();
      }
    
      public select() {
        if (this.returnMyObjects)
          return createDummyMyObjectsListObservable();
        return Observable.empty();
      }
    
      public setReturnObjects(wantMyObjects: boolean) {
        this.returnMyObjects = wantMyObjects;
      }
    }
    

    这是目前为止的测试。无论我自己尝试什么样的教程,再加上其他教程,这都是行不通的,我真的很难弄清楚从哪里开始。在下面的示例中,actions没有属性“next”,因为它是可观察的,即使我直接从迁移指南中复制了它。expect()。“Should also work”中的toBe()测试也不正确,但我找不到正确语法的文档。

    我的影响。影响。规范ts

    import {TestBed} from "@angular/core/testing";
    import {MyObjectServiceMock, StorageMock, StoreMock} from "../../../test-config/mocks-ionic";
    import {Store} from "@ngrx/store";
    import {LogService} from "../../services/log/log.service";
    import {MyEffects} from "./myEffects.effects";
    import {MyObjectService} from "../../services/myObject.service";
    import {provideMockActions} from "@ngrx/effects/testing";
    import {Observable} from "rxjs/Observable";
    import {cold, hot} from 'jasmine-marbles';
    import {InitMyObjectsCompleteAction, LoadAllAction, LoadAllCompleteAction} from "../actions/myObject.actions";
    import {Storage} from "@ionic/storage";
    import {ToastService} from "../../services/toast.service";
    import {ToastController} from "ionic-angular";
    import {ReplaySubject} from "rxjs/ReplaySubject";
    
    describe('MyObject Effects', () => {
      let myObject: MyObjectEffects;
      let actions: Observable<any>;
    
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          providers: [
            MyObjectEffects,
            provideMockActions(() => actions),
            ToastService,
            ToastController,
            LogService,
            {provide: Store, useClass: StoreMock},
            {provide: MyObjectService, useClass: MyObjectServiceMock},
            {provide: Storage, useClass: StorageMock}
          ]
        })
        myObjectEffects = TestBed.get(MyObjectEffects);
      });
    
      it('should work', () => {
        actions = hot('--a-', {a: InitMyObjectsCompleteAction});
        const expected = cold('--b', {b: LoadAllCompleteAction});
    
        expect(myObjectEffects.loadAllCompleteAction$).toBeObservable(expected);
      });
    
     it('should work also', () => {
        actions = new ReplaySubject(1);
    
        actions.next(new LoadAllAction());
    
        myObjectEffects.loadAllCompleteAction$.subscribe(result => {
          expect(result).toBe(new LoadAllCompleteAction());
        });
      });
    
    }); 
    

    基本上可以归结为两个问题:

    1、如何以正确的方式配置测试台,以及我是否必须 嘲笑一切?

    2、如何正确测试 是否按上述和代码中所述调度操作?

    很明显,我不是要你为我写测试,但也许你知道我可以从哪里开始,或者给我指出正确的方向。非常感谢,我真的很感激!

    1 回复  |  直到 7 年前
        1
  •  0
  •   TobiDevloft    8 年前

    找到了解决方案,也许它会在将来帮助别人。

    1. 在beforeach()中从测试床注入效果和模拟服务

      service = TestBed.get(MyObjectService); myObjecEffects = TestBed.get(MyObjectEffects);

    2. 将操作模拟为Action类型的ReplaySubject:

      provideMockActions(() => actions)

    3. 订阅我要在此过程中测试的每个效果:

      myObjectEffects.loadAllAction$.subscribe((result) => {})

    4. 在每个要测试的调用或其他内容的订阅测试中,并向actions主题提供新值:

      myObjectEffects.loadAllAction$.subscribe((result) => actions.next(result)) .

    5. 对要测试的每个效果重复此操作。

    由于这可能不是很清楚,下面是整个测试:

    describe('MyObject Effects', () => {
      let myObjectEffects: MyObjectEffects;
      let actions: ReplaySubject<Action>;
      let service: MyObjectService;
    
    
      beforeEach(() => {
        actions = new ReplaySubject(1);
        TestBed.configureTestingModule({
          declarations: [MyObjectsApp],
          imports: [
            IonicModule.forRoot(MyObjectsApp)
          ],
          providers: [
            MyObjectEffects,
            provideMockActions(() => actions),
            ToastService,
            ToastController,
            LogService,
            {provide: MyObjectService, useClass: MyObjectServiceMock},
            {provide: Store, useClass: StoreMock}
          ]
        });
        service = TestBed.get(MyObjectService);
        myObjectEffects = TestBed.get(MyObjectEffects);
      });
    
      it('loadAll action should eventually save objects', () => {
        actions.next(new LoadAllAction());
        spyOn(service, 'loadObjects').and.returnValue(Observable.of([new Object({id: 20})]));
        myObjectEffects.loadAllAction$.subscribe((result) => {
          expect(service.loadObjects).toHaveBeenCalledTimes(1);
          expect(result).toEqual(new LoadAllCompleteAction([new Object({id: 20})]));
          actions.next(result);
        });
        objectEffects.loadAllCompleteAction$.subscribe((result) => {
          expect(result).toEqual(new SaveObjectsCompleteAction());
        });
      })
    });
    
    推荐文章