Don’t lose your Marbles when testing time-dependent RxJS

Alan Batruk
AppLearn Engineering
3 min readMay 20, 2022

Before you read on I should say, this is not a full guide on how to use RxJS Marbles. It is simply some advice to use Marbles whenever you testing more complex RxJS.

This short post will touch on where I used to go wrong before doing this myself, some of the knowledge I’ve acquired since and show a few examples of how I now use it to complete my tests.

I have had a few occasions to write tests for RxJS and every time there was a delay involved I had problems. After spending a good amount of time and seeing numerous failing tests, I sought help from my AppLearn colleagues. They suggested to try using RxJS Marbles.

Suddenly, after no more time spent than before I was not only able to write passing tests, but much better tests than before.

I realised that my mistake was trying to use jasmine clock and asynchronous test (async / await or done ). The lesson I learned was that both methods are just not suitable to properly test RxJS observables that are time dependent.

My journey with Marbles started from this point, and it’s proven to be an excellent tool.

TestScheduler is used to test asynchronous code synchronously and deterministically by virtualising time. With that you can then use Marbles diagrams to visually represent behaviour of an observable.

One note here. Remember that observables you are passing to test this way cannot be asynchronous i.e. Promise and from. Dependency injection is your friend here.

A deep dive on Marbles syntax can be found here, but I just want to touch briefly on those that helped me to achieve my goal.

  • - 1 frame of virtual time
  • [0-9][ms|s|m] time progression
  • | successful completion of an observable
  • # error terminating the observable
  • [a-z0-9] value emitted by an observable

There are also several TestScheduler helper functions all are listed here, but I only used one for my purpose:

expectObservable(actual: Observable<T>, subscriptionMarbles?: string).toBe(marbleDiagram: string, values?: object, error?: any)

Finally, here’s how they look together when using TestScheduler:

it('should return expected', () => {
const scheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});

scheduler.run((helpers) => {
const { expectObservable } = helpers;
const expectedMarbleDiagram = 'a-b|';

expectObservable(observableToTest).toBe(expectedMarbleDiagram);
});
});

In terms of examples, here we have retry function that after a given number of attempts will throw an error.

And here you can see how simple is to test it using Marbles.

As you can see, it is really easy pattern to follow.

Another example I use is a custom RxJS operator that buffers emitted values during debounce time.

And again, here’s a simple Marbles test for it:

If you’d like to play with this yourself, you can do so here in StackBlitz.

In conclusion, do yourself a favour by using TestScheduler and Marbles whenever you need to test some RxJS code. If you start here, you’ll save yourself a lot of time and trouble when testing time-dependent observables.

--

--