10

我刚刚开始为我的应用程序编写 e2e 测试,并且遇到了 Protractor 和 ngrx/effects 的超时问题。

我有以下效果每几分钟调度一个动作:

@Effect() setSessionTimer$ = this.actions$
        .ofType(Auth.ActionTypes.SET_SECONDS_LEFT)
        .map(toPayload)
        .switchMap(secondsLeft => Observable.concat(
            Observable.timer((secondsLeft - 60) * 1000).map(_ => new Auth.SessionExpiringAction(60)),
            Observable.timer(60 * 1000).map(_ => new Auth.SessionExpiredAction())
        ));

尝试运行量角器测试会导致测试超时并出现以下错误,因为 Angular 不稳定。

失败:等待异步 Angular 任务在 11 秒后完成超时。这可能是因为当前页面不是 Angular 应用程序。有关更多详细信息,请参阅常见问题解答: https ://github.com/angular/protractor/blob/master/docs/timeouts.md#waiting-for-angular 在等待带有定位器的元素时 - 定位器:By(css 选择器,.工具栏标题)

根据这个问题(https://github.com/angular/protractor/issues/3349),我需要使用 NgZone 在 Angular 之外运行一个区间 Observable。我尝试了不同的组合,this.ngZone.runOutsideAngular()但没有任何效果,并且测试不断超时。

例如,这不起作用:

@Effect() setSessionTimer$ = this.actions$
        .ofType(Auth.ActionTypes.SET_SECONDS_LEFT)
        .map(toPayload)
        .switchMap(secondsLeft => this.ngZone.runOutsideAngular(() => Observable.concat(
            Observable.timer((secondsLeft - 60) * 1000).map(_ => new Auth.SessionExpiringAction(60)),
            Observable.timer(60 * 1000).map(_ => new Auth.SessionExpiredAction())
        )));

我不知道如何在 Angular 之外运行效果。有没有人成功地 e2e 测试了他们的 ngrx 应用程序?

4

2 回答 2

32

解决方案是安排可观察的计时器在 NgZone 之外运行,然后在发生有趣的事情时重新进入该区域。

首先,您将需要两个实用函数来包装任何调度程序并导致效果进入或离开区域:

import { Subscription } from 'rxjs/Subscription';
import { Scheduler } from 'rxjs/Scheduler';
import { NgZone } from '@angular/core';


class LeaveZoneSchduler {
  constructor(private zone: NgZone, private scheduler: Scheduler) { }

  schedule(...args: any[]): Subscription {
    return this.zone.runOutsideAngular(() => 
        this.scheduler.schedule.apply(this.scheduler, args)
    );
  }
}

class EnterZoneScheduler {
  constructor(private zone: NgZone, private scheduler: Scheduler) { }

  schedule(...args: any[]): Subscription {
    return this.zone.run(() => 
        this.scheduler.schedule.apply(this.scheduler, args)
    );
  }
}

export function leaveZone(zone: NgZone, scheduler: Scheduler): Scheduler {
  return new LeaveZoneSchduler(zone, scheduler) as any;
}

export function enterZone(zone: NgZone, scheduler: Scheduler): Scheduler {
  return new EnterZoneScheduler(zone, scheduler) as any;
}

然后使用调度程序(如asapor async),您可以使流进入或离开区域:

import { async } from 'rxjs/scheduler/async';
import { enterZone, leaveZone } from './util';

actions$.ofType('[Light] Turn On')
    .bufferTime(300, leaveZone(this.ngZone, async))
    .filter(messages => messages.length > 0)
    .observeOn(enterZone(this.ngZone, async))

请注意,大多数基于时间的运算符(如bufferTimedebounceTimeObservable.timer等)已经接受了替代调度程序。只有当有趣的事情发生时,您才需要observeOn重新进入该区域。

于 2017-04-03T12:30:53.020 回答
15

对于 Angular 6 和 RxJS 6,使用以下代码:

import { SchedulerLike, Subscription } from 'rxjs'
import { NgZone } from '@angular/core'

class LeaveZoneScheduler implements SchedulerLike {
  constructor(private zone: NgZone, private scheduler: SchedulerLike) { }

  schedule(...args: any[]): Subscription {
    return this.zone.runOutsideAngular(() =>
      this.scheduler.schedule.apply(this.scheduler, args)
    )
  }

  now (): number {
    return this.scheduler.now()
  }
}

class EnterZoneScheduler implements SchedulerLike {
  constructor(private zone: NgZone, private scheduler: SchedulerLike) { }

  schedule(...args: any[]): Subscription {
    return this.zone.run(() =>
      this.scheduler.schedule.apply(this.scheduler, args)
    )
  }

  now (): number {
    return this.scheduler.now()
  }
}

export function leaveZone(zone: NgZone, scheduler: SchedulerLike): SchedulerLike {
  return new LeaveZoneScheduler(zone, scheduler)
}

export function enterZone(zone: NgZone, scheduler: SchedulerLike): SchedulerLike {
  return new EnterZoneScheduler(zone, scheduler)
}

效果应该是这样的:

import { asyncScheduler, queueScheduler } from 'rxjs'
import { filter, observeOn, bufferTime } from 'rxjs/operators'
import { enterZone, leaveZone } from './util';

actions$.ofType('[Light] Turn On')
  .pipe(
    bufferTime(300, leaveZone(this.ngZone, asyncScheduler)),
    filter(messages => messages.length > 0),
    observeOn(enterZone(this.ngZone, queueScheduler)),
  )
于 2018-06-01T07:31:35.810 回答