4

Sometimes we have functions, which modifies original source using async operators like delay.

Lets assume we have something really simple:

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

I am using marbles to test RxJs, so my test looks like:

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

It's pretty the same what is used in rxjs library for testing: https://github.com/ReactiveX/rxjs/blob/master/spec/operators/delay-spec.ts

But we have to patch Observable.delay function, because we don't have direct access to it. And it works good.

But we decided to start using pipeable operators from RxJs. Is there any ideas how test for this function:

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

could look like?

JsBin demo

Liam
  • 27,717
  • 28
  • 128
  • 190
ilyabasiuk
  • 4,270
  • 4
  • 22
  • 35

1 Answers1

0

UPD

Time progression syntax is available in rxjs from version 6.0.0. So your test could look like:

it('generate the stream correctly', () => {
    testScheduler.run(helpers => {
        const { cold, expectObservable, expectSubscriptions } = helpers;
        const input = ' -a-b-c|';
        const expected = '-- 9ms a 9ms b 9ms (c|)';
        /*
        // Depending on your personal preferences you could also
        // use frame dashes to keep vertical aligment with the input
           const input = ' -a-b-c|';
           const expected = '------- 4ms a 9ms b 9ms (c|)';
        // or
           const expected = '-----------a 9ms b 9ms (c|)';

        */

        const result = cold(input).pipe(
            concatMap(d => of(d).pipe(
                delay(10)
            ))
        );

        expectObservable(result).toBe(expected);
   });

Original Answer

Finally found some workaround, not sure that it's applicable to all cases.

So here is file with our modify functions:

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

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

The main idea here is import all operators as object and that put a spy on method of this objects:

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

The problem here is that case I got error:

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

To avoid this error I just changed property descriptor in object:

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

So my spec file now looks like :

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);
    });
});
Liam
  • 27,717
  • 28
  • 128
  • 190
ilyabasiuk
  • 4,270
  • 4
  • 22
  • 35