Testing Observables in Angular

Netanel Basal
Netanel Basal
Published in
3 min readApr 4, 2018

--

In this article, I’d like to talk about a misconception I’ve read in other articles about writing tests for observables in Angular.

Let’s examine this basic example we’re all familiar with.

todos component spec

We have data service that uses the Angular HTTP library to return cold observable.

Other articles around the web suggest that, in order to test the above component, we can create a stub service that returns an of() observable.

todos component spec

You run the code above. The test passes, and you are 👯 👯 👯 👯 👯

That’s what I call cheating 🙈

By default, the of() observable is synchronous, so you’re basically making asynchronous code synchronous.

Let’s demonstrate this with a small add-on to our code.

todos component

We added a loading element that should be visible when the request begins but hidden when the subscription function is called. (i.e., the request finished successfully)

Let’s test it.

todos component spec

As you likely imagined, the above test will never pass. You will get the error:

Expected null not to be null.

When we run detectChanges(), the ngOnInit() hook will run and execute the subscription function synchronously, causing the isLoading property to always be false. As a result, the test always fails.

You probably remember the old days, when we wrote tests in AngularJS and stub promises like this:

spyOn(todosService,'get').and.returnValue(deferred.promise);

The code above is completely valid — unlike observables, promises are always asynchronous.

Let me show you three ways to correct the above.

Using defer()

Those who’d rather stick to promises can return a defer() observable that will return a promise with the data.

todos component spec

We use defer() to create a block that is only executed when the resulting observable is subscribed to.

Using Schedulers

Schedulers influence the timing of task execution. You can change the default schedulers of some operators by passing in an extra scheduler argument.

todos component spec

The async scheduler schedules tasks asynchronously by setting them on the JavaScript event loop queue. This scheduler is best used to delay tasks in time or schedule tasks at repeating intervals.

You can read more about schedulers here.

Using jasmine-marbles

RxJS marble testing is an excellent way to test both simple and complex observable scenarios.

Marble testing uses a similar marble language to specify your tests’ observable streams and expectations.

todos component spec

This test defines a cold observable that waits two frames (--), emits a value (x), and completes (|). In the second argument, you can map the value marker (x) to the emitted value (todos).

Here are a few more resources to learn about marble testing:

  1. The official docs.
  2. Marble testing Observable Introduction.
  3. RxJS Marble Testing: RTFM

Follow me on Medium or Twitter to read more about Angular, Vue and JS!

👂🏻 Last but Not Least, Have you Heard of Akita?

Akita is a state management pattern that we’ve developed here in Datorama. It’s been successfully used in a big data production environment for over seven months, and we’re continually adding features to it.

Akita encourages simplicity. It saves you the hassle of creating boilerplate code and offers powerful tools with a moderate learning curve, suitable for both experienced and inexperienced developers alike.

I highly recommend checking it out.

--

--

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.