LiveLoveApp logo

Getting Started wtih Marble Tests

Codesandbox

As a reminder, Codesandbox uses Jest by default in the client environment that we are executing our tests within.

Example Test

Let's start to bring this all together and look at a sample test.

import { interval } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { filter, map, tap } from 'rxjs/operators';

describe('getting started with RxJS testing with marbles', () => {
  let testScheduler: TestScheduler;

  beforeEach(() => {
    testScheduler = new TestScheduler((actual, expected) =>
      expect(actual).toEqual(expected)
    );
  });

  test('say hello world then complete', () => {
    testScheduler.run(({ cold, expectObservable }) => {
      const values = {
        a: 'hello',
        b: 'world'
      };
      const source = cold('-a-b-|', values);
      const expected = '   -a-b-|';
      expectObservable(source).toBe(expected, values);
    });
  });

  test('filter odd values', () => {
    testScheduler.run(({ cold, expectObservable }) => {
      const values = {
        a: 0,
        b: 1,
        c: 2,
        d: 3,
        e: 4,
        f: 5
      };
      const source = cold('abcdef|', values).pipe(filter((value) => value % 2 === 0));
      const expected = '   a-c-e-|';
      expectObservable(source).toBe(
        expected,
        values
      );
    });
  });
});

See example on codesandbox

After setting up the TestScheduler we have two tests. First, we have a test that mocks an Observable that has to next notifications and then completes. Second, we have a test that mocks an Observable that filters odd values.

Let's break down the first test:

  • We define a values object whose property keys are the character that represents the value in the marble string, and the value of the property is the value of the next notification.
  • We create a new cold Observable using the cold() function from the RunHelpers object, specifying the marble string, and the values. The marble string describes an Observable that emits a next notification value in frames 1 and 3. Characters a and b represent each next notification. The pipe ( | ) character represents a completion notification on frame 5.
  • We define a new expected string that represents the Observable behavior that we expect. To start with, we're not modifying the source observable at all, so we should expect an identical behavior of that which we mocked using the cold() function.
  • Finally, we use the expectObservable() function to assert the source to be deeply equal to the expected, and provide the values for the expected Observable.

The second test is a bit more interesting. In this test we are going to assert that source Observable filters out odd values.

Let's break down the second test:

  • First, we define the values object for our next notifications.
  • We create a new cold Observable using the cold() function, specifying the marble string, and the values. The source Observable emits 6 next notification in sequence from frame 0 to frame 5, and then completes on frame 6. Note that on the source cold Observable that we are using the filter() operator to filter the next notifications. The predicate function provided to the filter() operator returns true when the value is even.
  • The expect string describes the behavior after filtering odd values. We should expect to only receive a next notification where the value is even in frames 0, 2, and 4. The Observables should then complete in frame 6.
  • Finally, we use the expectObservable() function to perform the assertion.

Subscriptions

We can also use a marble string to describe the timing of an Observer subscribing using a carrot character ( ^ ). Further, we can indicate the timing of an Observer unsubscribing using the exclamation mark ( ! ).

Here is an example of a hot Observable source, an Observer's subscription, and the expected Observable:

const source = hot('  --a--b--c--d--e--f');
const subscription = '-----^------!';
const expected = '    -----b--c--d|';

In the example above you'll notice that we have an additional subscription string that describes the Observer's subscribing, and unsubscribing, behaviors. As indicated, we only expect to receive next notifications while the Observer is subscribed to the source Observable.