How to marble test RxJS Subjects
How to bridge the gap between the timing of function calls and Observables

Asynchronous code is hard! It’s hard to write and to test.
RxJs has changed the way we think about asynchrony. Instead of using Promises, we nowadays deal with streams in the form of Observables or Subjects. RxJs provides us with many out of the box operators to create, merge, or transform streams.
But we do not only get great tools for runtime code, but we also get amazing tools to test streams.
Marble diagrams are a great way of modeling streams. They are used throughout many tutorials and can now also be used in tests to assert streams.
In case you never heard of marble testing I recommend you to checkout the following article.
Marble testing is great! Many people use it to test their Observables. But what about Subjects?
A pizza service 🍕
Nowadays, most frameworks use the concept of components. When working with components, you often need a way of communication between them. Usually, you have multiple ways of implementing communication between components.
Angular, for example, offers component communication over @Input
and @Output
, communication over a service or, for a more sophisticated use case, the use of a state management library such as ngrx.
For simple use cases, a service with a Subject is often a nice solution. Imagine an app where you can order Pizzas.

A pizza can be ordered by clicking a button on a list item. The button click then uses the OrderService
to inform the shopping cart that a new pizza has been added. Let’s have a look at the code of the OrderService
.

Our OrderService
has a public orderPizza
method that will next
a new Pizza into our pizza$
.
☝ ️You should never expose a
Subject
directly. It’s always better to only expose an Observable. To do so you can always use theasObservable
method of a Subject. To keep things as simple as possible and focus on the testing Problem we leave that away for now.
Okay cool. We implemented an impeccable service. Let’s test it. 😃
There are multiple ways to test such services. We can use the classic “subscribe and assert” pattern or “marble testing.” If you want to find out more about their differences and which you should use, I recommend you to check out this article:
We are interested in marble testing this service.
Marble test a subject 🧪
To get started, we first instantiate our sut
(system under test) and the TestScheduler
.

When instantiating the TestScheduler
we pass a assertDeppEqual
function which tells the TestScheduler
how to compare values. From here on, we can start using the TestScheduler
to write our marble test.

We want to test that the correct Pizza is emitted by our pizza$
Subject.
We use destructuring to get a hold of the TestScheduler’s
expectObservable
and cold
helper methods. We then use the expectObservable
function in combination with the Marble syntax to ensure that our expected Observable is an Observable that streams one value, which is “Tonno.”
Easy, isn’t it? Let’s run it.
Error: expect(received).toEqual(expected) // deep equality- Expected - 11
+ Received + 1- Array [
- Object {
- "frame": 0,
- "notification": Notification {
- "error": undefined,
- "hasValue": true,
- "kind": "N",
- "value": "Tonno",
- },
- },
- ]
+ Array []
Oh no. 😲 Our test fails. What happened?
If you like this blog post and want to get updated about new cool things that happen in modern frontend development — follow me on Twitter.
Let’s analyze this. The error message says that we expected a value of “Tonno” to be emitted at frame
0, but we didn’t receive any value. Why is that?
Let’s take a closer look at the function calls and the order in which they are executed.
- We call the
orderPizza
function in our test
sut.orderPizza(pizzaTonno);
2. The pizzaTonno
gets “nexted” into our Subject
.
this.pizza$.next(pizza);
3. Our test subscribes to the exposed Subject
expectObservable(sut.pizza$).toBe('a', {a: pizzaTonno});
From this order, we can see that it's a timing issue. The value is “nexted” into the Subject
before it’s subscribed. After we subscribed, no value is streamed. Therefore we receive an empty array.
We need to bridge the gap between the timing of function calls and Observables.
Bridging the gap 🌉
To adjust this test, we need to change the timing of the orderPizza
function call. Instead of calling it immediately, we need to call it in the same frame as the subscription.
To do so, we can use the TestScheduler’s
cold
helper method. As the name indicates, the cold
function creates a cold Observable from a marble Diagram. We can subscribe
to such an Observable and call the orderPizza
function.
cold('-a').subscribe(() => sut.orderPizza(pizzaTonno))
Our complete test then looks like this:

We bridged the gap, and our test is now green. We successfully marble tested a simple service with a Subject.
For sure, in this simple example, it would also be possible to use the “subscribe and assert” pattern. But then we would be forced to deal with the subscription and the done
callback.
It’s up to you to decide which approach is more concise and easier to understand.
Further resources about Marble testing
If you want to get to know more in-depth detail on marble testing and the TestScheduler
you may be interested in one of my articles about RxJS and testing.
🧞 🙏 If you liked this post, share it and give some claps👏🏻 by clicking multiple times on the left side's clap button.
Claps help other people to discover content and motivate me to write more. 😉