DEV Community

Noor Ul Haq
Noor Ul Haq

Posted on

Marble Testing With RxJS

I recently came across a nice way of testing observables called “marble testing”. It’s called marble testing because marble diagrams, the diagrams in the documentation of the library, are used to assert behavior and values, and create mock observables for the observable under test.

Making a marble diagram in code

Marble diagrams are usually pictures, but in our tests, a marble diagram is simply a string which looks something like this:

const input$ = "--a----b--c|";

It represents events occurring over “virtual time”.

Syntax

-: Represents a frame, and it is equal to 1ms of virtual time for our observable. It is possible to configure the amount of virtual time.
[a-z0-9]: Represents a value being emitted by the observable, and advances time by one frame.
(abc): Groups multiple values that are expected to be emitted in a single frame. It also advances virtual time by number values emitted plus 2 for ().
[0-9](ms|s|m): Represents amount of virtual time, you can use it as a replacement of -.
|: Represents a complete signal i.e., observable has completed and has nothing more to emit.
#: Represents an error being thrown from the observable.
^(only in ‘hot’ observables): Represents the point in time where a subscription is expected, and represent the 0 frame, so --^--a--b--| shows that a subscription is expected at ^. The frames before ^ are -ve, and the ones after it are +ve.
!: Represent unsubscription point.

Note: Both ^ and ! can be used to assert when was an observable was subscribed and unsubscribed to, and also to guide when should the observable being tested be subscribed and unsubscribed to. I’ve added a few example that’ll make it more clear.

Hot and Cold Observables

Before we begin writing tests it is important that we understand the difference between hot and cold observables. There are a few ways to describe hot and cold observables, so I’d suggest reading up on it a bit here.

The easiest explanation is that in a hot observable, the producer is not part of the observable and it emits values whether it has any subscribers or not, for example an observable over mouse move event.

A cold observable emits values only when it is subscribed to; the producer is created when the observable is subscribed to, for example an ajax request with ajax operator.

Learn more about hot and cold observable.

Time to write some tests

Let’s test an observable that emits two values with an interval of 10ms, increments them by 1 and then completes afterwards.

simple observable to test

Import TestScheduler from rxjs/testing, instantiate it with the function to perform the assertion. I’m using TestScheduler for simplicity but you can also use rxjs-marbles or jest-marbles for writing marble tests.

import TestScheduler

Let’s finally write our test. We can represent input$ behavior in marble diagram as 10ms a 9ms (b|). Why is there a 9ms if the values are emitted after 10ms? because, just like -, a symbol representing a value also advances the frame by 1ms of virtual time, so when a will be emitted, 11ms of virtual time will have passed and because of that, second value b will be emitted 9ms after a and the observable will complete on that frame which is why the complete signal is grouped with b.

simple test for a simple observable

We passed a function to scheduler.run() which will be called with some helpers to mock hot and cold observables for the observable under test, queue assertions etc. One these helpers is expectObservable, we’ll use it to queue our assertion. Assertions are executed synchronously after our callback is executed. We can also run assertions while our callback is being executed by calling helpers.flush() but scheduler.run() does that for us anyway.

Let’s write another test for an observable that subscribes over an observable of input events.

another simple observable to test

our test will look something like this:

another simple test for a simple observable

One more thing you can control, is when the TestScheduler subscribes to and unsubscribes from the observable under test. expectObservable helper takes a second string argument called “subscription marble” that does that.

using subscription marble to subscribe and unsubscribe

With the subMarble, TestScheduler is being instructed to subscribe to output$ a frame before input$ emits any value and unsubscribe from output$ two frames after it emits its first value. Because of early unsubscription TestScheduler only receives one value i.e., a, which is why we had to update outputMarbles and values.

Hopefully this post has given you enough understanding to start writing your tests and jump into documentation.

Thanks.

Top comments (0)