如何在Ionic 3中测试ngrx /效果?

时间:2018-01-31 11:16:55

标签: angular ionic-framework jasmine ionic3 ngrx

我试图在Ionic中为我的ngrx效果编写一些测试,但无法让它工作。在线有几个教程,但它们不适用于我的案例或不适用。 official ngrx migration guide中的示例,例如不再起作用了。

我想测试loadAllAction $效果处理LoadAllAction的情况,最后调度LoadAllCompleteAction。

这是我的代码。我将类和服务重命名为更清晰,所以如果有拼写错误请忽略它。实际代码中的导入也都是正确的。

myEffects.effects.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))
  }
}

mocks.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;
  }
}

这是迄今为止的测试。无论我自己尝试什么教程,结合其他人 - 它根本不起作用,而且我很难找到从哪里开始。在下面的示例中,操作不具有“下一个”属性&#39;因为它是一个可观察的,尽管我从迁移指南中直接复制了它。 &#39;中的expect()。toBe()也应该工作&#39; - 测试也不正确 - 但我找不到正确语法的文档。

myEffects.effects.spec.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());
    });
  });

}); 

基本上归结为2个问题:

1。如何以正确的方式配置TestBed,我甚至必须这样做     嘲笑一切?

2。我如何正确地测试正确的过程     如上所述和代码中的调度操作?

我显然没有要求你为我写测试 - 但也许你知道我可以在哪里开始或指出我正确的方向。非常感谢,我真的很感激!

1 个答案:

答案 0 :(得分:0)

想出来,也许是将来帮助某人的解决方案。

  1. 在beforeEach()

    中从TestBed中注入效果和模拟服务

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

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

    provideMockActions(() => actions)

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

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

  4. 在您要测试的任何电话或其他内容的每个订阅测试中,并为操作主题提供新值:

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

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

  6. 由于这可能不是很清楚,所以这是整个测试:

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