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 anactual
Observable, and returns an object that contains a singletoBe
property. ThetoBe
property is a function that accepts themarbles
string as the first argument along with an optionalvalues
argument in order to assert theactual
Observable to theexpected
result described in themarbles
syntax.expectSubscriptions()
provides the setup for an assertion of anactual
Observable, and returns an object that contains a singletoBe
property. ThetoBe
property is a function that accepts themarbles
string and asserts theactual
Observable's subscription occurrences to those that are described in themarbles
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 thevalues
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 theexpectObservable()
orexpectSubscription
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 therxjs/testing
module. - We then declare a new
describe
block. The code within eachdescribe
block is executed before any of the actual tests. We'll use this as an opportunity to declare a block-leveltestScheduler
variable that each of our tests will use. - Within the
beforeEach()
lifecycle method we create a new instance of theTestScheduler
. The constructor function accepts aassertDeepEqual
argument, which is a function that will be invoked to by theTestScheduler
instance to assert a deep equality check. The function is invoked with theactual
andexpected
values. We are using theexpect()
andtoEqual()
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 therun()
method to execute our tests. Therun()
method is invoked with acallback
function, which receives theRunHelpers
object.