单元测试RxJS Observable.timer使用typescript,karma和jasmine

时间:2016-08-26 13:23:18

标签: unit-testing angular rxjs5

嗨,我对Angular2,Karma和Jasmine相对较新。目前我正在使用Angular 2 RC4 Jasmine 2.4.x. 我有一个Angular 2服务,定期调用这样的http服务:

getDataFromDb() { return Observable.timer(0, 2000).flatMap(() => {
        return this.http.get(this.backendUrl)
            .map(this.extractData)
            .catch(this.handleError);
    });
}

现在我想测试一下这个功能。出于测试目的,我刚刚测试了" http.get"在没有Observable.timer的单独函数上执行:

const mockHttpProvider = {
    deps: [MockBackend, BaseRequestOptions],
    useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
        return new Http(backend, defaultOptions);
    }
}

describe('data.service test suite', () => {
    var dataFromDbExpected: any;

    beforeEachProviders(() => {
        return [
            DataService,
            MockBackend,
            BaseRequestOptions,
            provide(Http, mockHttpProvider),
        ];
    });

    it('http call to obtain data',
        inject(
            [DataService, MockBackend],
            fakeAsync((service: DataService, backend: MockBackend) => {
                backend.connections.subscribe((connection: MockConnection) => {
                    dataFromDbExpected =  'myData';
                    let mockResponseBody: any = 'myData';
                    let response = new ResponseOptions({ body: mockResponseBody });
                    connection.mockRespond(new Response(response));

                });
                const parsedData$ = service.getDataFromDb()
                    .subscribe(response => {
                        console.log(response);
                        expect(response).toEqual(dataFromDbExpected);
                    });
            })));
});

我显然想用Observable.timer测试整个函数。我想有人可能想要使用rxjs框架中的TestScheduler,但是如何判断只重复计时器函数x次?我无法在打字稿上下文中找到任何使用它的文档。

编辑:我使用的是rxjs 5 beta 6

编辑:为Angular 2.0.0最终版本添加了工作示例:

describe('when getData', () => {
    let backend: MockBackend;
    let service: MyService;
    let fakeData: MyData[];
    let response: Response;
    let scheduler: TestScheduler;

    beforeEach(inject([Http, XHRBackend], (http: Http, be: MockBackend) => {
        backend = be;
        service = new MyService(http);
        fakeData = [{myfake: 'data'}];
        let options = new ResponseOptions({ status: 200, body: fakeData });
        response = new Response(options);

        scheduler = new TestScheduler((a, b) => expect(a).toEqual(b));
        const originalTimer = Observable.timer;
        spyOn(Observable, 'timer').and.callFake(function (initialDelay, dueTime) {
            return originalTimer.call(this, initialDelay, dueTime, scheduler);
        });
    }));
    it('Should do myTest', async(inject([], () => {
        backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
        scheduler.schedule(() => {
            service.getMyData().subscribe(
                myData => {
                    expect(myData.length).toBe(3,
                        'should have expected ...');
                });
        }, 2000, null);
        scheduler.flush();
    })));
});

4 个答案:

答案 0 :(得分:7)

您需要将TestScheduler注入beforeEach部分中的timer方法:

i+1

之后,您可以使用for (int i = 0; i < max.size; ++i) { printf("%d", i); if((i+1)%4 == 0) printf("\n"); } 完全控制时间:

expect -c 'bin/installto.sh /var/www/mail/rc >/dev/null 2>&1'
expect "Do you want to continue? (y/N)"
send "y\n"
interact

您需要beforeEach(function() { this.scheduler = new TestScheduler(); this.scheduler.maxFrames = 5000; // Define the max timespan of the scheduler const originalTimer = Observable.timer; spyOn(Observable, 'timer').and.callFake(function(initialDelay, dueTime) { return originalTimer.call(this, initialDelay, dueTime, this.scheduler); }); }); 才能启动TestScheduler。

编辑:因此,如果您只想测试X次,请按常规使用计划功能(并使用正确的绝对时间,以毫秒为单位)。

edit2:我添加了缺少的调度程序启动

edit3:我改了它所以应该使用RxJs5

edit4:添加scheduleAbsolute设置,因为默认值为750毫秒,并且会阻止测试运行时间更长的序列。

答案 1 :(得分:1)

我遇到TestScheduler()方法的问题,因为schedule()箭头函数永远不会执行,所以我找到了另一条路径。

Observable.timer函数只返回一个Observable,所以我从头创建了一个让我完全控制。

首先,为观察者创建一个var:

let timerObserver: Observer<any>;

现在在beforeEach()创建间谍并让它返回一个Observable。在Observable中,将实例保存到计时器:

beforeEach(() => {
  spyOn(Observable, 'timer').and.returnValue(Observable.create(
    (observer => {
      timerObserver = observer;
    })
  ));
});

在测试中,只需触发Observable:

it('Some Test',()=>{
  // do stuff if needed

  // trigger the fake timer using the Observer reference
  timerObserver.next('');
  timerObserver.complete();

  expect(somethingToHappenAfterTimerCompletes).toHaveBeenCalled();
});

答案 2 :(得分:0)

我也为此挣扎了一段时间。自从问了这个问题以来,框架显然已经发生了很多变化,所以我认为也许有人会对我的解决方案有所帮助。我的项目使用rxjs 5,茉莉2.8和angular5。

在我的组件中,计时器每分钟用于调用服务中的http-get函数。我的问题是,在使用fakeAsync区域时,从未调用(存根)get函数,并且收到错误消息:“错误:队列中仍存在1个周期性计时器。”

由于计时器不断触发并且在测试结束时没有停止而显示错误。可以通过添加“ discardPeriodicTasks();”来解决。到测试结束,这将导致计时器停止。蜱();可用于伪造时间的流逝,直到下一次通话。我在服务中的get函数上使用了一个间谍,以查看它是否有效:

  it(
    'should call getTickets from service every .. ms as defined in refreshTime',
    fakeAsync(() => {
      fixture.detectChanges();
      tick();
      expect(getTicketsSpy).toHaveBeenCalledTimes(1);
      // let 2 * refreshtime pass
      tick(2 * component.refreshTime);
      expect(getTicketsSpy).toHaveBeenCalledTimes(3);
      discardPeriodicTasks();
    })
  );

refreshTime是我在计时器中使用的参数。我希望这可以防止某人花半天时间来解决这个问题。

答案 3 :(得分:0)

您可以使用fakeAsync()轻松测试Observable计时器。这是一个显示倒数计时器的组件(使用momentJS持续时间):

timeout.component.ts

@Component({
  selector: 'app-timeout-modal',
  templateUrl: './timeout-modal.component.html'
})
export class TimeoutModalComponent implements OnInit {
  countdownTimer: Observable<number>;
  countdownSubscription: Subscription;
  durationLeft = moment.duration(60000); // millis - 60 seconds

  ngOnInit() {
    this.countdownTimer = Observable.timer(0, 1000);
    this.countdownSubscription = this.countdownTimer
      .do(() => this.durationLeft.subtract(1, 's'))
      .takeWhile(seconds => this.durationLeft.asSeconds() >= 0)
      .subscribe(() => {
        if (this.durationLeft.asSeconds() === 0) {
        this.logout();
      }
    });
  }
}

timeout.component.spec.ts

beforeEach(async(() => {
    ...
}));

beforeEach(() => {
    fixture = TestBed.createComponent(TimeoutModalComponent);
    component = fixture.componentInstance;
});

it('should show a count down', fakeAsync(() => {
    fixture.detectChanges();
    expect(component.durationLeft.asSeconds()).toEqual(60);
    tick(1000);
    fixture.detectChanges();
    expect(component.durationLeft.asSeconds()).toEqual(59);

    component.countdownSubscription.unsubscribe();
}));