LiveLoveApp logo

TestScheduler

TestScheduler

The TestScheduler class is part of the rxjs/testing module, and enables synchronous testing of asynchronous, or synchronous, Observables.

The primary method that we'll be using is run(). This method is invoked with a callback function, which receives a RunHelpers object.

Let's take a look at the RunHelpers interface:

export interface RunHelpers {
  cold: typeof TestScheduler.prototype.createColdObservable;
  hot: typeof TestScheduler.prototype.createHotObservable;
  flush: typeof TestScheduler.prototype.flush;
  expectObservable: typeof TestScheduler.prototype.expectObservable;
  expectSubscriptions: typeof TestScheduler.prototype.expectSubscriptions;
}

As we can see in the interface above, the RunHelpers object contains several properties that are methods that we will use for wiring up our tests. Let's take a look at each property:

  • expectObservable() provides the setup for an assertion of an actual Observable, and returns an object that contains a single toBe property. The toBe property is a function that accepts the marbles string as the first argument along with an optional values argument in order to assert the actual Observable to the expected result described in the marbles syntax.
  • expectSubscriptions() provides the setup for an assertion of an actual Observable, and returns an object that contains a single toBe property. The toBe property is a function that accepts the marbles string and asserts the actual Observable's subscription occurrences to those that are described in the marbles syntax.
  • cold() provides a cold observable that emits notification(s) upon subscription. The marble syntax string defines the sequencing of notifications. And, we can optionally specify the values of each notification using either an object or an array.
  • hot() provides a hot observable. The primary distinction between a cold and hot observable when testing is the ability to test for notification upon subscribing.
  • flush() provides the ability to manually execute the scheduled assertions created using either the expectObservable() or expectSubscription function.

Let's further clarify the details of the flush() function. As indicated, the flush() function manually executes the scheduled assertions. It is important to understand that when we define assertions using either the expectObservable() or expectSubscription() functions that the assertions are not executed synchronously, or in other words, at the time the statement is executed by the test runner. Rather, the assertions are executed when the callback function to the run() function returns.

If we need to test side effects that are created by our Observable then we will need to manually execute the tests using the flush() function in order to use our testing library's assertion function on the side effect.

Finally, we should note that the TestScheduler can be used with any testing library and test runner. In these examples we'll be using Jest.

Example

Let's take a look at an example of using the TestScheduler and the run() method:

import { TestScheduler } from 'rxjs/testing';

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 }) => {
      // todo
    });
  });
});

Let's review the example code above:

  • First, we import the TestScheduler class from the rxjs/testing module.
  • We then declare a new describe block. The code within each describe block is executed before any of the actual tests. We'll use this as an opportunity to declare a block-level testScheduler variable that each of our tests will use.
  • Within the beforeEach() lifecycle method we create a new instance of the TestScheduler. The constructor function accepts a assertDeepEqual argument, which is a function that will be invoked to by the TestScheduler instance to assert a deep equality check. The function is invoked with the actual and expected values. We are using the expect() and toEqual() function provided by Jest in order to perform the deep equality check.
  • Finally, we use the test() function to create a new test block. Within each test block we'll use the run() method to execute our tests. The run() method is invoked with a callback function, which receives the RunHelpers object.