Тестирование асинхронных конвейерных операторов в RxJS

Иногда у нас есть функции, которые изменяют исходный источник, используя асинхронные операторы, такие как задержка.

Предположим, у нас есть что-то действительно простое:

// Old syntax
function modify(source) {
   return source.delay(1000);
}

Я использую шарики для тестирования RxJ, поэтому мой тест выглядит так:

it("mock chain style call (modify function)", function() {
    var values = {
      a: "test",
      x: "test"
    }; 

    var source = hot(    "-a", values);
    var delayTime = time( "--|");
    var result = cold(   "---x", values);

    var originalDelay = Rx.Observable.prototype.delay;
    spyOn(Rx.Observable.prototype, "delay").and.callFake(function () { 
       return originalDelay.call(this, delayTime, jm.getTestScheduler()); 
    });

    expect(modify(source)).toBeObservable(result);
});

Это почти то же самое, что используется в библиотеке rxjs для тестирования: https://github.com/ReactiveX/rxjs/blob/master/spec/operators/delay-spec.ts

Но мы должны пропатчить функцию Observable.delay, потому что у нас нет прямого доступа к ней. И это хорошо работает.

Но мы решили начать использовать конвейерные операторы из RxJ. Есть ли идеи, как проверить эту функцию:

// New syntax
function modify(source) {
   return source.pipe(Rx.operators.delay(1000));
}

может выглядеть?

JsBin демо

1 ответ

Наконец-то нашел обходной путь, не уверен, что это применимо для всех случаев.

Итак, вот файл с нашими функциями модификации:

import { Observable } from "rxjs";
import { delay } from "rxjs/operators";

export function modify<T>(source: Observable<T>): Observable<T> {
    return source.pipe(delay(1000));
}

Основная идея здесь состоит в том, чтобы импортировать все операторы как объект, и это шпионит за методом этих объектов:

import * as operators from "rxjs/operators";
... 
spyOn(operators, "delay")

Проблема здесь в том, что я получил ошибку:

Error: <spyOn> : delay is not declared writable or has no setter

Чтобы избежать этой ошибки, я просто изменил дескриптор свойства в объекте:

/** 
 * Changes property descriptor to make possible to use spyOn function for imported 
 *  modules.
 */
 function spyOnOperator(obj: any, prop: string): any {
    const oldProp: Function = obj[prop];
    Object.defineProperty(obj, prop, {
        configurable: true,
        enumerable: true,
        value: oldProp,
        writable: true
    });

   return spyOn(obj, prop);
}

Итак, мой файл спецификаций теперь выглядит так:

import { cold, getTestScheduler, hot, time } from "jasmine-marbles";
import * as operators from "rxjs/operators";
import { modify } from "./modify";

describe("Test delay: ", () => {
    it("mock chain style call (modify function)", () => {
        const originalDelay: Function = operators.delay;
        const values: any = {
            a: "test",
            x: "test"
        };

        const source: TestHotObservable = hot("-a", values);
        const delayTime: number = time("--|");
        const result: TestColdObservable = cold("---x", values);

        spyOnOperator(operators, "delay").and.callFake(() => {
            return originalDelay.call(this, delayTime, getTestScheduler());
        });

        expect(modify(source)).toBeObservable(result);
    });
});
Другие вопросы по тегам