How I do testing of RxJs4 and RxJs5 code

Since starting to use RxJs I have been searching for the best and easiest approach for testing. After getting a setup which worked pretty fine they introduced RxJs5 which is largely incompatible with their previous testing approach. This blogpost documents my search for a solution of a way to keep testing in RxJs5 simple.

Testing in RxJs4

The documentation about how to test with RxJs4 is pretty good. Depending on the case i tend to take one of two roads to testing Rx code: dumb it down to promises or full-fledged testing using the TestScheduler.

using Promises

Depending on your needs you can cast the Rx Observable stream toPromise() and verify the output. The pro of this approach is that not much scaffolding is required. The con is that you do not have access to the timings of emissions.

it('can be done using .toPromise()', () => {
  const rxPromise = Rx.Observable.from([2,4,6,8])
    .filter(val => val > 4)
    .toArray() /* because only the last value or error will be passed to .toPromise() */
    .toPromise();

  return rxPromise
    .then(
      res => expect(res).to.equal([6,8]),
      err => { throw new Error(`expected result but got error: ${err.message}`)}
    );
});

By returning the promise to the it() operator you let the test framework (in my case Mocha) handle the promise without having to pass the done callback to your tests and invoke that yourself.

Using the TestScheduler

You can also create an instance of the TestScheduler which gives you acces to virtual time testing. This is also required when you start doing tests with operators which are time-bound like .Delay or .Interval. All these operators take an optional scheduler as last argument for you to replace the default implementation.

Because the testScheduler invokes your Rx stream in virtual time you will need to advance it yourself procedurally or call .startScheduler and let it run its course:

const onNext = Rx.ReactiveTest.onNext;
const onCompleted = Rx.ReactiveTest.onCompleted;

it('can be done using the TestScheduler', () => {
  const scheduler = new Rx.TestScheduler();

  const results = scheduler.startScheduler(
    () => Rx.Observable.interval(100, scheduler).take(3),
    { created: 100, subscribed: 200, disposed: 1000 } /* note, this are the default settings and i only include these for clarity */
  );

  collectionAssert.assertEqual(res.messages, [
    onNext(200 + 100, 0),
    onNext(200 + 200, 1),
    onNext(200 + 300, 2),
    onCompleted(200 + 300)
  ]);
});

The collectionAssert basic implementation can be found in the RxJs4 documentation

Testing in RxJs5 the RxJs4 way

When you migrate your codebase from RxJs4 towards 5 you will find out that a lot of things have been moved, renamed and above all that the implementation of the TestScheduler is no longer available. RxJs contributor kwonoj has created a compatibility shim to help migration towards RxJs5. You can install it using npm npm install @kwonoj/rxjs-testscheduler-compat. Not all features of the TestScheduler are implemented but the most important .startScheduler is working.

const TestScheduler = require('@kwonoj/rxjs-testscheduler-compat').TestScheduler;
const next = require('@kwonoj/rxjs-testscheduler-compat').next;
const complete = require('@kwonoj/rxjs-testscheduler-compat').complete;

it('works in RxJs5 with the compat package', () => {
  const scheduler = new TestScheduler(); // Note; no longer the Rx.TestScheduler

  const results = scheduler.startScheduler(
    () => Rx.Observable.interval(100, scheduler).take(3),
    { created: 100, subscribed: 200, unsubscribed: 1000 } // NOTE: disposed is now renamed to unsubscribed
  );

  collectionAssert.assertEqual(res.messages, [
    next(200 + 100, 0),
    next(200 + 200, 1),
    next(200 + 300, 2),
    complete(200 + 300)
  ]);
});

I have adapted my own version of the collectionAssert to work with RxJs5 in combination with the compatiblity package and is available as a gist

Testing in RxJs5 using the new Marble testing syntax

The RxJs team has introduced marble testing syntax to more visually define how your operator or custom code should operate.

    var e1 = hot('----a--^--b-------c--|');
    var e2 = hot(  '---d-^--e---------f-----|');
    var expected =      '---(be)----c-f-----|';

    expectObservable(e1.merge(e2)).toBe(expected);

At the time of writing this post they have not yet made this approach really easy to use outside of the RxJs5 library itself. There are implementations available to see how to do it yourself. You can also look around in the codebase of RxJs5 to see how to setup your testing framework to do your own marble tests. There is an open issue about documenting testing with RxJs5. I have not yet succeeded to get my testing framework setup to do marble testing in this way.

Conclusions

I would love to start using the marble diagram syntax for my testing because they are less verbose in the setup and communicate the emission of values over time way better than the emissions array filled with onNext/onError/onCompleted statements in RxJs4. But for now it seems just a bit out of reach.

Since the compatibility shim is a great workaround i will stay using it to write tests for RxJs5 as i did for RxJs4 until the marble diagram syntax becomes easier to start using.

Show Comments