Testing

    or a more shorthand version:

    Point is you try to avoid the whole timing thing. Rxjs have historically, in Rxjs 4 provided the approach of using a TestScheduler with its own internal clock, which has enabled you to increment time. This approach have had two flavors :

    Approach 1

    1. let testScheduler = new TestScheduler();
    2. // my algorithm
    3. let stream$ = Rx.Observable
    4. .interval(1000, testScheduler)
    5. .take(5);
    6. // setting up the test
    7. let result;
    8. stream$.subscribe(data => result = data);
    9. testScheduler.advanceBy(1000);
    10. assert( result === 1 )
    11. testScheduler.advanceBy(1000);
    12. ... assert again, etc..

    This approach was pretty easy to grok. The second approach was using hot observables and a startSchedule() method, looking something like this :

    1. // setup the thing that outputs data
    2. var input = scheduler.createHotObservable(
    3. onNext(100, 'abc'),
    4. onNext(200, 'def'),
    5. onNext(250, 'ghi'),
    6. onNext(300, 'pqr'),
    7. onNext(450, 'xyz'),
    8. onCompleted(500)
    9. );
    10. // apply operators to it
    11. var results = scheduler.startScheduler(
    12. function () {
    13. return input.buffer(function () {
    14. return input.debounce(100, scheduler);
    15. })
    16. .map(function (b) {
    17. return b.join(',');
    18. });
    19. },
    20. {
    21. created: 50,
    22. subscribed: 150,
    23. disposed: 600
    24. }
    25. );
    26. //assert
    27. collectionAssert.assertEqual(results.messages, [
    28. onNext(400, 'def,ghi,pqr'),
    29. onNext(500, 'xyz'),
    30. ]);

    A little harder to read IMO but you still get the idea, you control time because you have a TestScheduler that dictates how fast time should pass.

    This is all Rxjs 4 and it has changed a bit in Rxjs 5. I should say that what I am about to write down is a bit of a general direction and a moving target so this chapter will be updated, but here goes.

    In Rxjs 5 something called Marble Testing is used. Yes that is related to Marble Diagram i.e you express your expected input and actual output with graphical symbols.

    First time I had a look at the I was like What now with a what now?. But after writing a few tests myself I came to the conclusion this is a pretty elegant approach.

    So I will explain it by showing you code:

    1. // setup
    2. const lhsMarble = '-x-y-z';
    3. const expected = '-x-y-z';
    4. const expectedMap = {
    5. x: 1,
    6. z : 3
    7. };
    8. const lhs$ = testScheduler.createHotObservable(lhsMarble, { x: 1, y: 2, z :3 });
    9. const myAlgorithm = ( lhs ) =>
    10. Rx.Observable
    11. .from( lhs );
    12. const actual$ = myAlgorithm( lhs$ );
    13. //assert
    14. testScheduler.expectObservable(actual$).toBe(expected, expectedMap);
    15. testScheduler.flush();

    Setup

    1. const lhsMarble = '-x-y-z';
    2. const expected = '-x-y-z';
    3. const expectedMap = {
    4. x: 1,
    5. y: 2,
    6. z : 3
    7. };
    8. const lhs$ = testScheduler.createHotObservable(lhsMarble, { x: 1, y: 2, z :3 });

    We essentially create a pattern instruction -x-y-z to the method createHotObservable() that exist on our TestScheduler. This is a factory method that does some heavy lifting for us. Compare this to writing this by yourself, in which case it corresponds to something like:

    The reason we don’t do it ourselves is that we want the TestScheduler to do it, so time passes according to its internal clock. Note also that we define an expected pattern and an expected map:

    1. const expected = '-x-y-z';
    2. const expectedMap = {
    3. x: 1,
    4. y: 2,
    5. z : 3
    6. }

    Thats what we need for the setup, but to make the test run we need to flush it so that TestScheduler internally can trigger the HotObservable and run an assert. Peeking at createHotObservable() method we find that it parses the marble patterns we give it and pushes it to list:

    1. // excerpt from createHotObservable
    2. var messages = TestScheduler.parseMarbles(marbles, values, error);
    3. var subject = new HotObservable_1.HotObservable(messages, this);
    4. this.hotObservables.push(subject);
    5. return subject;

    Next step is assertion which happens in two steps
    1) expectObservable()
    2) flush()

    The expect call pretty much sets up a subscription to our HotObservable

    1. // excerpt from expectObservable()
    2. this.schedule(function () {
    3. subscription = observable.subscribe(function (x) {
    4. var value = x;
    5. // Support Observable-of-Observables
    6. if (x instanceof Observable_1.Observable) {
    7. value = _this.materializeInnerObservable(value, _this.frame);
    8. }
    9. actual.push({ frame: _this.frame, notification: Notification_1.Notification.createNext(value) });
    10. }, function (err) {
    11. actual.push({ frame: _this.frame, notification: Notification_1.Notification.createError(err) });
    12. actual.push({ frame: _this.frame, notification: Notification_1.Notification.createComplete() });
    13. });
    14. }, 0);

    by defining an internal schedule() method and invoking it.
    The second part of the assert is the assertion itself:

    1. // excerpt from flush()
    2. while (readyFlushTests.length > 0) {
    3. var test = readyFlushTests.shift();
    4. this.assertDeepEqual(test.actual, test.expected);

    It ends up comparing two lists to each other, the actual and expect list.
    It does a deep compare and verifies two things, that the data happened on the correct time frame and that the value on that frame is correct. So both lists consist of objects that looks like this:

    1. {
    2. frame : [some number],
    3. notification : { value : [your value] }
    4. }

    Both these properties must be equal for the assert to be true.

    I havn’t really explained what we looked at with:

    But it actually means something. - means a time frame passed. a is just a symbol. So it matters how many - you write in actual and expected cause they need to match. Let’s look at another test so you get the hang of it and to introduce more symbols:

    1. const lhsMarble = '-x-y-z';
    2. const expected = '---y-';
    3. const expectedMap = {
    4. x: 1,
    5. y: 2,
    6. z : 3
    7. };
    8. const lhs$ = testScheduler.createHotObservable(lhsMarble, { x: 1, y: 2, z :3 });
    9. const myAlgorithm = ( lhs ) =>
    10. Rx.Observable
    11. .from( lhs )
    12. .filter(x => x % 2 === 0 );
    13. const actual$ = myAlgorithm( lhs$ );
    14. //assert
    15. testScheduler.expectObservable(actual$).toBe(expected, expectedMap);
    16. testScheduler.flush();

    In this case our algorithm consists of a filter() operation. Which means 1,2,3 will not be emitted, only 2. Looking at the ingoing pattern we have:

    1. '-x-y-z'

    And expected pattern

    1. `---y-`

    And this is where you clearly see that no of - matters. Every symbol you write, be it - or x etc, happens at a certain time, so in this case when x and z wont occur due to the filter() method it means we just replace them with - in the resulting output so

    1. -x-y

    becomes

    1. ---y

    because x doesn’t happen.

    There are of course other symbols that are of interest that lets us define things like an error. An error is denoted as a # and below follows an example of such a test:

    And here is another symbol | representing a stream that completes:

    1. const lhsMarble = '-a-b-c-|';
    2. const expected = '-a-b-c-|';
    3. const expectedMap = {
    4. a : 1,
    5. b : 2,
    6. c : 3
    7. };
    8. const myAlgorithm = ( lhs ) =>
    9. Rx.Observable
    10. .from( lhs );
    11. const lhs$ = testScheduler.createHotObservable(lhsMarble, { a: 1, b: 2, c :3 });
    12. const actual$ = lhs$;
    13. testScheduler.expectObservable(actual$).toBe(expected, expectedMap);
    14. testScheduler.flush();

    Happy testing