So what is RxJS VirtualTimeScheduler?

Alexander Poshtaruk
Angular In Depth
Published in
6 min readNov 6, 2019

--

AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

You may already know that Observables produce values over time. And the moment when exactly value will be emitted is controlled by a special entity — scheduler. But what is VirtualTimeScheduler? Is it possible to emit values in a virtual time? More about it in this article!

Prerequisites: you can read about RxJS Schedulers in this article or can watch this video.

Why?

To understand why you may need VirtualTimeScheduler first let's try to test such code:

getData(timeSec) {
return this.http.get('some_url')
.pipe(
repeatWhen((n) => n.pipe(
delay(timeSec * 1000),
take(2)
))
);
}

Here we make HTTP call with this.http.get function and if it succeeds it will repeat HTTP calls two more times after specified timeout.

So obviously AsyncScheduler is used by RxJS delay operator. (You remember — you can get familiar with schedulers here or here 😎)

The simplest method for testing async code is by using jasmine done callback. When a specific test is executed, jasmine holds and waits until done callback is called. So if data is provided asynchronously — we subscribe to them, run assertion when after we got all the data and then run done callback to say jasmine that we can continue. The test may look like this:

it('should emit 3 specific values', (done) => {
const range$ = service.getData(0.01);
const result = [];
mockHttp = {get: () => of(42, asyncScheduler)};


range$.subscribe({
next: (value) => {
result.push(value);
},
complete: () => {
expect(result).toEqual([42, 42, 42]);
done();
}
});

});

And in this example a drawback of jasmine done callback is becoming obvious — we cannot test code that has some big delays time. Since otherwise tests run will be too long.

How to omit it?

We can provide a smaller delay time. That's why in test code you can see that we call getData with very little value. You may ask — how to test code that uses big-time delays? And before RxJS version 6 provided TestScheduler.run method and time-progressive syntax marbles (you remember — this article is not about marble testing but about VirtualTimeScheduler😉)— we could use RxJS VirtualTimeScheduler for that.

If you are interesting in TestScheduler.run and all this modern testing RxJS staff - you may read nice articles by @kevinkreuzer or you can watch my free video-course: "RxJS unit testing in Angular application. The whole picture."

VirtualTimeScheduler

So testing Observables with jasmine done callback method is not good for big delay values. And this is where VirtualTimeScheduler can help us. It provides a virtual time mechanism so you can emulated passed time inside RxJS scheduler to run delayed tasks instantly.

How VirtualTimeScheduler works

In RxJS If we want to emit values with some specified delays — then AsyncScheduler is used. AsyncScheduler uses setInterval internally to schedule value emissions. What we need for testing such code is to make somehow run scheduled tasks instantly but with keeping the order of values.

VirtualTimeScheduler can do that since it inherits from AsyncScheduler and re-defined some internal methods.

If we replace AsyncScheduler instance with VirtualTimeScheduler instance in our function — then VirtualTimeScheduler prevents calling real setInterval and put a task in an internal queue (sorted by delay)

And when we call VirtualTimeScheduler instance flush function — all the delayed values will be emitted with keeping the order of values. Here you can see how it is implemented under the hood:

You may observable that we can also specify maxFrames threshold, so only specific tasks are executed which are delayed not more than the threshold value.

Now our code has to be changed — new scheduler param is added:

getData(timeSec, scheduler = asyncScheduler) {
return this.http.get('some_url')
.pipe(
repeatWhen((n) => n.pipe(
delay(timeSec * 1000, scheduler),
take(2)
))
);
}

Test code will look like:

it('should emit 3 specific values', () => {
const scheduler = new VirtualTimeScheduler();
service.http = {get: () => of(42, scheduler)};

const range$ = service.getData(30, scheduler); // scheduler param
const result = [];

range$.subscribe({
next: (value) => {
result.push(value);
}
});

scheduler.flush();
expect(result).toEqual([42, 42, 42]);
});

Here are steps for writing unit tests with VirtualTimeScheduler:

  1. We feed VirtualTimeScheduler to our code instead of AsyncScheduler. So there should be another scheduler param in our methods.

2. Then we get observable and subscribe to it. (you remember that cold observable is started only on subscription)

3. After that, we call flush() method that makes virtual time pass.

4. And check the final result

VirtualTimeScheduler vs TestScheduler

VirtualTimeScheduler has a child class — TestScheduler. TestScheduler has methods for marble testing.

Since TestScheduler is a child class of VirtualTimeScheduler we can take our tests with VirutlaTimeScheduler and replace its instance with TestScheduler instance.

But there are some small things you have to know not to waste time for possible issues debugging:

  1. Testscheduler maxFrame value is set to 750 milliseconds — this is done as sanity checks for marble testing. So if your code delays are bigger then that value — you should reassign it. In our case, since we do not run marble testing — we have to assign Infinity value to prevent test blocking.

2. TestScheduler also demands assertion expression when you want to create an instance. This assertion is used in marble testing.

Not to test your patience — I provide the same test code with TestScheduler:

it('should emit 3 specific values', () => {
const assertion = (actual, expected) => {
expect(actual).toEqual(expected);
};
const scheduler = new TestScheduler(assertion);
scheduler.maxFrames = Number.POSITIVE_INFINITY;

service.http = {get: () => of(42, scheduler)};

const range$ = service.getData(30, scheduler);
const result = [];

range$.subscribe({
next: (value) => {
result.push(value);
}
});

scheduler.flush();
expect(result).toEqual([42, 42, 42]);
});

Using TestScheduler just like VirtualTimeScheduler has the same imperfection — it is not visual and we can check only the final result. But marble testing and progressive time syntax can solve all these challenges. How? You can find it in free video-course "RxJS unit testing in Angular application. The whole picture."

Conclusion

Time to sum up.

VirtualTimeScheduler method has such pros:

  1. We can provide real production delay values
  2. We can test even hardcoded values — since timespans will pass instantly.

But this method has such imperfections:

  1. It allows testing only the final result (after Observable is complete).
  2. And additional method param is needed
  3. "Using the VirtualTimeScheduler won’t help if the code under test also includes time-related, non-RxJS code, as the virtual (RxJS level mocking) and fake time (setTimeout, setInterval monkey patching) concepts differ significantly." by Nicholas Jamieson
    To omit this con you should use fakeAsync Angular helper function instead of VirtualTimeScheduler.
*Remarks: After releasing RxJS version 6 with TestScheduler.run method you should definitely use marble testing for making unit-tests. All test examples with VirtualTimeScheduler in this article were provided just to understand the topic better. But of cause may be used if you find it suitable.

More to read

  1. "Tools for RxJS code unit testing in Angular 8 apps"
  2. "RxJS: Testing with Fake Time"
  3. "Testing Asynchronous Code in Angular Using FakeAsync"
  4. Articles by Kevin Kreuzer about RxJS unit testing.

Did you like the article? Clap👏 🤓

Let’s keep in touch on Twitter!

*BTW. I started a video tutorial series “Angular can waste your time” on Youtube. There I will be publishing videos about resolving tricky cases with Angular and RxJS. Take a look!

--

--

Alexander Poshtaruk
Angular In Depth

Senior Front-End dev in Tonic, 'Hands-on RxJS for web development' video-course author — https://bit.ly/2AzDgQC, codementor.io JS, Angular and RxJS mentor