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

大理石测试中的角度NgRx效应误差:预期$.length=0等于2。/应为$[0]=未定义为相等对象

  •  0
  • Felipe  · 技术社区  · 6 年前

    我得到的错误是:

    Expected $.length = 0 to equal 2.
    Expected $[0] = undefined to equal Object({ frame: 10, notification: Notification({ kind: 'N', value: LoadOrderLogisticStatusSuccess({ payload: Object({ 1047522: Object({ status: 0, partner: Object({ id: 1, slug: 'loggi' }), eta: '2020-06-09 10:00', pickupEta: '2020-06-09 12:00' }) }), type: 'load-order-logistic-status-success' }), error: undefined, hasValue: true }) }).
    Expected $[1] = undefined to equal Object({ frame: 20, notification: Notification({ kind: 'C', value: undefined, error: undefined, hasValue: false }) }).
    

    效果如下:

    @Injectable()
    export class OrderLogisticStatusEffects {
      loadOrdersLogisticStatus$ = createEffect(() =>
        this.actions$.pipe(
          ofType(LOAD_ORDERS_LOGISTIC_STATUS),
          withLatestFrom(this.store$.pipe(select(orderLogisticsStatusPollingIntervalSelector))),
          switchMap(([action, pollingInterval]) =>
            timer(0, pollingInterval).pipe(
              withLatestFrom(this.store$.pipe(select(selectedCompanySelector))),
              switchMap(([timerNum, company]) => this.loadOrdersLogisticStatus(pollingInterval, company))
            )
          )
        )
      );
    
      constructor(private actions$: Actions, private orderLogisticStatusService: OrderLogisticStatusService, private store$: Store<AppState>) {}
    
      private loadOrdersLogisticStatus(
        pollingInterval: number,
        company: Company
      ): Observable<LoadOrderLogisticStatusSuccess | LoadOrderLogisticStatusFail> {
        if (!company?.logisticsToken) {
          return of(new LoadOrderLogisticStatusFail(new Error('No company selected')));
        }
    
        this.orderLogisticStatusService.getOrdersStatus(company.logisticsToken).pipe(
          timeout(pollingInterval),
          map((result) => new LoadOrderLogisticStatusSuccess(result)),
          catchError((error) => {
            if (error.name === 'TimeoutError') {
              console.warn('Timeout error while loadin logistic status service', error);
            } else {
              console.error('Error loading order logistic status', error);
              Sentry.captureException(error);
            }
    
            return of(new LoadOrderLogisticStatusFail(error));
          })
        );
      }
    }
    

    这是我的测试:

    fdescribe('Order Logistic Status Effect', () => {
      let actions$: Observable<Action>;
      let effects: OrderLogisticStatusEffects;
    
      describe('With a selected company', () => {
        beforeEach(() => {
          const mockState = {
            ordersLogisticStatus: {
              pollingInterval: 10,
            },
            company: {
              selectedCompany: {
                logisticsToken: 'ey.xxxx.yyyy',
              },
            },
          };
    
          TestBed.configureTestingModule({
            providers: [
              { provide: OrderLogisticStatusService, useValue: jasmine.createSpyObj('orderLogisticsStatusServiceSpy', ['getOrdersStatus']) },
              OrderLogisticStatusEffects,
              provideMockActions(() => actions$),
              provideMockStore({
                selectors: [
                  {
                    selector: orderLogisticsStatusPollingIntervalSelector,
                    value: 30,
                  },
                  {
                    selector: selectedCompanySelector,
                    value: {
                      logisticsToken: 'ey.xxxx.yyy',
                    },
                  },
                ],
              }),
            ],
          });
    
          effects = TestBed.inject<OrderLogisticStatusEffects>(OrderLogisticStatusEffects);
        });
    
        it('should sucessfully load the orders logistics status', () => {
          const service: jasmine.SpyObj<OrderLogisticStatusService> = TestBed.inject(OrderLogisticStatusService) as any;
          service.getOrdersStatus.and.returnValue(cold('-a|', { a: mockData }));
    
          actions$ = hot('a', { a: new LoadOrdersLogisticStatus() });
          const expected = hot('-a|', {
            a: new LoadOrderLogisticStatusSuccess(mockData),
          });
    
          getTestScheduler().flush();
          expect(effects.loadOrdersLogisticStatus$).toBeObservable(expected);
        });
      });
    });
    
    const mockData = {
      1047522: {
        status: 0,
        partner: {
          id: 1,
        },
        eta: '2020-06-09 10:00',
        pickupEta: '2020-06-09 12:00',
      },
    };
    
    

    我的服务好像出了问题。即使我将其配置为返回一个冷可观察对象,但它似乎返回的是未定义的。

    有人能帮我吗?

    堆栈闪电战 : https://stackblitz.com/edit/angular-effects-test

    0 回复  |  直到 6 年前
        1
  •  1
  •   Andrei Gătej    6 年前

    StackBlitz .


    首先是 loadOrdersLogisticStatus

    loadOrdersLogisticStatus (/* ... */) {
      return this.orderLogisticStatusService.getOrdersStatus(/* ... */)
    }
    

    后来,我发现 jasmine-marbles 不设置 AsyncScheduler.delegate TestScheduler.run :

     run<T>(callback: (helpers: RunHelpers) => T): T {
      const prevFrameTimeFactor = TestScheduler.frameTimeFactor;
      const prevMaxFrames = this.maxFrames;
    
      TestScheduler.frameTimeFactor = 1;
      this.maxFrames = Infinity;
      this.runMode = true;
      AsyncScheduler.delegate = this;
    
      /* ... */
    }
    

    这是很重要的,因为当使用弹珠,一切都是 . 但在你的实现中,有一个 timer(0, pollingInterval) observable,默认情况下使用 AsyncScheduler . 未设置 AsyncScheduler.delegate代理 ,我们会有异步操作,我认为这是主要的问题。

    delegate 属性,我在 beforeEach() :

    AsyncScheduler.delegate = getTestScheduler();
    

    最后,我认为你的断言有一个小问题。您的effect属性似乎永远都不完整,而且您还使用 计时器(0,pollingInterval) . 所以我想你现在可以加上 take(N) 操作员以测试 N

    it("should sucessfully load the orders logistics status", () => {
      const service: jasmine.SpyObj<OrderLogisticStatusService> = TestBed.inject(OrderLogisticStatusService) as any;
      service.getOrdersStatus.and.callFake(() => cold('-a|', { a: mockData }));
    
      actions$ = hot('a', { a: new LoadOrdersLogisticStatus() });
      const expected = hot('-a--(b|)', {
        a: new LoadOrderLogisticStatusSuccess(mockData),
        b: new LoadOrderLogisticStatusSuccess(mockData),
      });
    
      expect(effects.loadOrdersLogisticStatus$.pipe(take(2))).toBeObservable(expected);
    });
    

    '-a--(b|)' - a 在第10帧发送,并且 b complete 通知(由于 take )发送时间 40th 框架,因为 pollingInterval 30 10 第二次通知( b类 )将被安排。

        2
  •  0
  •   Felipe    6 年前

    TestScheduler 对于 jasmine-marbles 不替换 AsyncScheduler return 中的语句 loadOrdersLogisticStatus

    所以为了让一切顺利,我必须做两件事:

    测试调度器 timer 可观察的是发射结果而不是 undefined 和以前一样。

    然而,我有另一个问题。这个 不会停止发射,所以我得到一个很长的错误流如下:

    Expected $.length = 26 to equal 2.
    Unexpected $[2] = Object({ frame: 60, notification: Notification({ kind: 'N', value: LoadOrderLogisticStatusSuccess({ payload: Object({ 1047522: Object({ status: 0, partner: Object({ id: 1, slug: 'loggi' }), eta: '2020-06-09 10:00', pickupEta: '2020-06-09 12:00' }) }), type: 'load-order-logistic-status-success' }), error: undefined, hasValue: true }) }) in array.
    Unexpected $[3] = Object({ frame: 90, notification: Notification({ kind: 'N', value: LoadOrderLogisticStatusSuccess({ payload: Object({ 1047522: Object({ status: 0, partner: Object({ id: 1, slug: 'loggi' }), eta: '2020-06-09 10:00', pickupEta: '2020-06-09 12:00' }) }), type: 'load-order-logistic-status-success' }), error: undefined, hasValue: true }) }) in array.
    Unexpected $[4] = Object({ frame: 120, notification: Notification({ kind: 'N', value: LoadOrderLogisticStatusSuccess({ payload: Object({ 1047522: Object({ status: 0, partner: Object({ id: 1, slug: 'loggi' }), eta: '2020-06-09 10:00', pickupEta: '2020-06-09 12:00' }) }), type: 'load-order-logistic-status-success' }), error: undefined, hasValue: true }) }) in array.
    Unexpected $[5] = Object({ frame: 150, notification: Notification({ kind: 'N', value: LoadOrderLogisticStatusSuccess({ payload: Object({ 1047522: Object({ status: 0, partner: Object({ id: 1, slug: 'loggi' }), eta: '2020-06-09 10:00', pickupEta: '2020-06-09 12:00' }) }), type: 'load-order-logistic-status-success' }), error: undefined, hasValue: true }) }) in array.
    Unexpected $[6] = Object({ frame: 180, notification: Notification({ kind: 'N', value: LoadOrderLogisticStatusSuccess({ payload: Object({ 1047522: Object({ status: 0, partner: Object({ id: 1, slug: 'loggi' }), eta: '2020-06-09 10:00', pickupEta: '2020-06-09 12:00' }) }), type: 'load-order-logistic-status-success' }), error: undefined, hasValue: true }) }) in array.
    Unexpected $[7] = Object({ frame: 210, notification: Notification({ kind: 'N', value: LoadOrderLogisticStatusSuccess({ payload: Object({ 1047522: Object({ status: 0, partner: Object({ id: 1, slug: 'loggi' }), eta: '2020-06-09 10:00', pickupEta: '2020-06-09 12:00' }) }), type: 'load-order-logistic-status-success' }), error: undefined, hasValue: true }) }) in array.
    Unexpected $[8] = Object({ frame: 240, notification: Notification({ kind: 'N', value: LoadOrderLogisticStatusSuccess({ payload: Object({ 1047522: Object({ status: 0, partner: Object({ id: 1, slug: 'loggi' }), eta: '2020-06-09 10:00', pickupEta: '2020-06-09 12:00' }) }), type: 'load-order-logistic-status-success' }), error: undefined, hasValue: true }) }) in array.
    Unexpected $[9] = Object({ frame: 270, notification: Notification({ kind: 'N', value: LoadOrderLogisticStatusSuccess({ payload: Object({ 1047522: Object({ status: 0, partner: Object({ id: 1, slug: 'loggi' }), eta: '2020-06-09 10:00', pickupEta: '2020-06-09 12:00' }) }), type: 'load-order-logistic-status-success' }), error: undefined, hasValue: true }) }) in array.
    

    前两个值是可以的,但其他值不是。所以我做的第二件事是使用 takeUntil 操作员取消订阅 定时器 一个测试已经完成(我不确定这是否是最好的方法,因为我不能在测试之外使用这个操作符,否则我的轮询将停止)。

    效果:

    @Injectable()
    export class OrderLogisticStatusEffects {
      loadOrdersLogisticStatus$ = createEffect(() => ({ scheduler = asyncScheduler, stopTimer = EMPTY } = {}) =>
        this.actions$.pipe(
          ofType(LOAD_ORDERS_LOGISTIC_STATUS),
          withLatestFrom(this.store$.pipe(select(orderLogisticsStatusPollingIntervalSelector))),
          switchMap(([action, pollingInterval]) =>
            timer(0, pollingInterval, scheduler).pipe(
              takeUntil(stopTimer),
              withLatestFrom(this.store$.pipe(select(selectedCompanySelector))),
              switchMap(([timerNum, company]) => this.loadOrdersLogisticStatus(pollingInterval, company))
            )
          )
        )
      );
    
      constructor(private actions$: Actions, private orderLogisticStatusService: OrderLogisticStatusService, private store$: Store<AppState>) {}
    
      private loadOrdersLogisticStatus(
        pollingInterval: number,
        company: Company
      ): Observable<LoadOrderLogisticStatusSuccess | LoadOrderLogisticStatusFail> {
        if (!company?.logisticsToken) {
          return of(new LoadOrderLogisticStatusFail(new Error('No company selected')));
        }
    
        return this.orderLogisticStatusService.getOrdersStatus(company.logisticsToken).pipe(
          timeout(pollingInterval),
          map((result) => new LoadOrderLogisticStatusSuccess(result)),
          catchError((error) => {
            if (error.name === 'TimeoutError') {
              console.warn('Timeout error while loadin logistic status service', error);
            } else {
              console.error('Error loading order logistic status', error);
              Sentry.captureException(error);
            }
    
            return of(new LoadOrderLogisticStatusFail(error));
          })
        );
      }
    }
    

    测试:

    fdescribe('Order Logistic Status Effect', () => {
      let actions$: Observable<Action>;
      let effects: OrderLogisticStatusEffects;
    
      describe('With a selected company', () => {
        beforeEach(() => {
          TestBed.configureTestingModule({
            providers: [
              { provide: OrderLogisticStatusService, useValue: jasmine.createSpyObj('orderLogisticsStatusServiceSpy', ['getOrdersStatus']) },
              OrderLogisticStatusEffects,
              provideMockActions(() => actions$),
              provideMockStore({
                selectors: [
                  {
                    selector: orderLogisticsStatusPollingIntervalSelector,
                    value: 30,
                  },
                  {
                    selector: selectedCompanySelector,
                    value: {
                      logisticsToken: 'ey.xxxx.yyy',
                    },
                  },
                ],
              }),
            ],
          });
    
          effects = TestBed.inject<OrderLogisticStatusEffects>(OrderLogisticStatusEffects);
        });
    
        it('should sucessfully load the orders logistics status', () => {
          const service = TestBed.inject(OrderLogisticStatusService) as jasmine.SpyObj<OrderLogisticStatusService>;
          service.getOrdersStatus.and.callFake(() => cold('a|', { a: mockData }));
    
          actions$ = hot('a', { a: new LoadOrdersLogisticStatus() });
          const expected = hot('a--b', {
            a: new LoadOrderLogisticStatusSuccess(mockData),
            b: new LoadOrderLogisticStatusSuccess(mockData),
          });
    
          const stopTimer = hot('----a', { a: 'stop' });
    
          const testScheduler = getTestScheduler();
          expect(effects.loadOrdersLogisticStatus$({ scheduler: testScheduler, stopTimer })).toBeObservable(expected);
        });
      });
    });
    
    const mockData = {
      1047522: {
        status: 0,
        partner: {
          id: 1,
          slug: 'loggi',
        },
        eta: '2020-06-09 10:00',
        pickupEta: '2020-06-09 12:00',
      },
    };
    
    
    推荐文章