Marble testing Observable Introduction

“Observation tower breaks through the clouds on a foggy morning in the city” by Tim Gouw on Unsplash

The way we tend to learn and communicate about observables and streams in general, is done with marble diagrams. It is a visual representation, like a schema describing what’s happening with our observable.

When we need to test Observables, we have two main patterns that we can use. Either the subscribe and assert pattern, or the marble testing pattern using diagrams. This article is an introduction to marble testing.

Marble diagram

Marble diagram is a domain specific language for RxJS to help you to model the interactions and the values of one or more observable in your test.

Because observable is a stream of events through time and some operators are directly affecting this time relation (delay, throttle…), it is a better and easier way to represent them visually with diagrams. You can see the observable values, the operator and the result through time.

What is a marble diagram | reactivex.io
In a marble diagram, time flows to the right, and the diagram describes how values (“marbles”) are emitted on the Observable execution.

This new syntax will provide you the power to create and manipulate the stream of your observable. For example:

of(1, 2, 3).pipe(map(x => x*2))

This code will create an observable that will emit a value. Then, the value will be multiplied by 2. We can represent this with a marble diagram like this:

+-----1---------2-----------3---->
+--------------------------------+
| |
| multiply by 2 |
| |
+--------------------------------+
+-----2---------4-----------6---->

Marble testing

Most of the documentation about Marble testing can be found here, but let see the key elements here.

To write a test with marble diagrams you will need to stick to a convention of characters that will help visualize the observable stream:

  • During the tests, the sens of time (when values are emitted) is handle by the RxJS TestScheduler
  • - (dash): simulate the passage of time, one dash correspond to a frame which can be perceived as 10ms in our tests, —--- is 40 ms
  • a-z (a to z): represent an emission, -a--b---c stands for “emit a at 20ms, b at 50ms, c at 90ms”
  • | (pipe): emit a completed (end of the stream), ---a-| stands for emit ‘a’ at 40ms then complete (60ms)
  • # (pound sign): indicate an error (end of the stream), —--a--# emit a at 40ms then an error at 70ms
  • ( ) (parenthesis): multiple values together in the same unit of time, —--(ab|) stands for emit a b at 40ms then complete (40ms)
  • ^ (caret): indicate a subscription point, —^-- subscription starting at ^
  • ! (exclamation point): indicate the end of a subscription point, —^--! subscription starting at ^ and ending at !

These strings are a powerful syntax that will permit you to simulate the passage of time, emit a value, a completion, an error etc.. all that, without creating the observable yourself.

You also have some methods to parse and create observables from your diagrams:

cold()

cold(marbles: string, values?: object, error?: any) Subscription starts when test begins:

cold(--a--b--|, { a: 'Hello', b: 'World' }) → Emit ‘Hello’ at 30ms and ‘World’ at 60ms, complete at 90ms.

hot()

hot(marbles: string, values?: object, error?: any) Behaves like subscription starts at point of caret:

hot(--^--a--b--|, { a: 'Hello', b: 'World' }) → Subscription begins at point of caret, then emit ‘Hello’ at 30ms and ‘World’ at 60ms, complete at 90ms.


There are multiple libraries for marble testing but we will use jasmine-marbles in the examples because we will be testing with jasmine, rxjs-marbles is another great implementation that is test framework agnostic.


Now if we want to create the marble test of the previous example, we can do as follow:

it('should multiply by "2" each value emitted', () => {
  const values = { a: 1, b: 2, c: 3, x: 2, y: 4, z: 6};
  const source = cold('-a-b-c-|', values);
  const expected = cold('-x-y-z-|', values);
  const result = source.pipe(map(x => x*2));
  expect(result).toBeObservable(expected);
});

Marble testing is really useful as this technique allows you to actually see the value emitted by your observable though time in a nice and simple manner.

Notice the source and expected variables declarations, in marble testing you will use this pattern a lot. With this pattern, you are able to see when the emitted values (a, b, c), when the subscription ends (|) for the source and can directly compare with what is expected.

You can see more example on StackBlitz below:

How it works

Marble diagrams are parsed, creating an observable that emits test message objects. The parsing step is done through the helper functions cold() and hot() .

When parsed, the resulting object message has some specific properties:

{
“frame”: 10,
”notification”: {
“kind”: ”N”,
”value”: ”a”,
”hasValue”: true
}
}

Test message object includes the values emitted, the frame at which thew were emitted, and the type of notification, including next, error, and complete. These objects will be tested by the expect clause in you tests.

Few examples

To show the basics of Marble testing, lets see some examples with different RxJS operators.

Map operator
SwitchMap operator
MergeMap operator
ConcatMap operator

More examples on StackBlitz below:

Using time in Marbles testing

When you need to test asynchrone code, Marble testing can be an elegant way to test it. It will be more visual, clearer and with less code.

When time comes into play, the test needs to have a time reference or context. We don’t want to have to deal with milliseconds, so we are changing the time reference to use a virtual clock, that will count in frames. This is the role of the TestScheduler.

All RxJS operators & utilities that handle the concept of time (delay, interval, debounce…) have a second argument where you can inject your Scheduler (the time reference). It will determine what “thing” you will use for the passage of time (milliseconds, frames).

Let see some examples with Interval and Delay:

Testing an interval with the TestScheduler
Testing a delay with the TestScheduler

Examples available on StackBlitz below:

Summary

In this article we have looked at what is Marbles Testing with Observables. We used some basic examples to show how it works. We then saw how to test Observable that has relation to time with some examples.

  • Marble testing has its own syntax
  • Marble testing is a visual way to test Observables
  • Easier to test, read and maintain
  • Easier way to test code affecting time