Модульный Тест RxJS Наблюдаем.таймер с использованием typescript, karma и jasmine


Привет я относительно новым для Angular2, кармы и Жасмин. В настоящее время я использую Angular 2 RC4 Jasmine 2.4.икс У меня есть служба Angular 2, которая периодически вызывает службу http следующим образом:

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

Теперь я хочу проверить функциональность. Для целей тестирования я только что протестировал " http.получить " на отдельную функцию без наблюдаемого.таймер, выполнив:

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

Я, очевидно, хочу проверить всю функцию с наблюдаемым.таймер. Я думаю, что можно было бы использовать TestScheduler из фреймворка rxjs, но как я могу сказать, чтобы только повторить функцию таймера для x раз? Я не смог найти никакой документации, использующей его в контексте машинописи.

Edit: я использую rxjs 5 beta 6

Edit: добавлен рабочий пример для Angular 2.0.0 final release:

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 4

4 ответа:

Вам нужно ввести TestScheduler в метод timer внутри части beforeEach:

beforeEach(function() {
  this.scheduler = new TestScheduler();  
  const originalTimer = Observable.timer;
  spyOn(Observable, 'timer').and.callFake(function(initialDelay, dueTime) {  
    return originalTimer.call(this, initialDelay, dueTime, this.scheduler);
  });
});

После этого вы полностью контролируете время с помощью scheduleAbsolute:

this.scheduler.schedule(() => {
  // should have been called once
  // You can put your test code here
}, 1999, null);

this.scheduler.schedule(() => {
  // should have been called twice
  // You can put your test code here
}, 2000, null);

this.scheduler.schedule(() => {
  // should have been called three times
  // You can put your test code here
}, 4000, null);

this.scheduler.flush();

Нужно scheduler.flush() чтобы начать TestScheduler.

Edit: поэтому, если вы хотите проверить его только X раз, используйте функции scheduleAbsolute так часто (и с правильным абсолютным временем в миллисекундах), как вы хотите.

Edit2: я добавил недостающий запуск планировщика

Edit3: я изменил его так должно быть работа с RxJs5

У меня были проблемы с подходом TestScheduler(), потому что функция стрелки schedule() никогда не выполнялась, поэтому я нашел другой путь.

Функция Observable.timer просто возвращает наблюдаемое, поэтому я создал его с нуля, чтобы дать мне полный контроль.

Сначала создайте var для наблюдателя:

let timerObserver: Observer<any>;

Теперь в beforeEach() Создайте шпиона и пусть он вернет наблюдаемое. Внутри наблюдаемого сохраните экземпляр в таймер:

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

В тесте просто запустите Наблюдаемый:

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

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

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

Я тоже некоторое время боролся с этим. Поскольку, по-видимому, многое изменилось в рамках с тех пор, как был задан этот вопрос, я подумал, что, возможно, кому-то поможет мое решение. Мой проект использует rxjs 5, jasmine 2.8 и angular 5.

В моем компоненте таймер использовался для вызова функции http-get в службе каждую минуту. Моя проблема заключалась в том, что при использовании зоны fakeAsync функция get (stubbed) никогда не вызывалась, и я получил сообщение об ошибке: "Ошибка: 1 периодический таймер(ы) все еще в очередь.".

Ошибка появляется, потому что таймер продолжает работать и не останавливается в конце теста. Это можно решить, добавив "discardPeriodicTasks ();" в конец теста, что приводит к остановке таймера. Tick (); может использоваться для подделки времени до следующего вызова. Я использовал шпиона на моей 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-это параметр, который я использовал в таймере. Я надеюсь, что это помешает кому-то потратить половину Дэй пытается разобраться в этом.

Вы можете довольно легко проверить наблюдаемые таймеры с помощью fakeAsync(). Вот компонент, который отображает таймер обратного отсчета (используя длительность momentJS):

Тайм-аут.деталь.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();
      }
    });
  }
}

Тайм-аут.деталь.спекуляция.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();
}));