Testing observable’s values when using Angular fakeAsync

Image for post
Image for post

The problem

I’ve found two main approaches when unit testing observables behavior in javascript:

The Zone.js fakeAsync() intercepts the asynchronous javascript features allowing for control of time. So, observables need no modification and, by default, they use the default scheduler and not the TestScheduler required by the marbles testing framework. While it is possible to make observables to use a provided scheduler, it is somewhat cumbersome and I had a lot of problemas with that route. So I decided to test the observables directly.

Also, there were this problem with the marbles testing framework. For testing an operator is awesome and very simple, but it is not adequate to test a real observable with its timely value emissions because the comparison to a mocked observable also takes into account the time when values are emitted. And since, probably, there is no need to assert on the time between emissions, comparing the observable value emissions to an array of values is adequate.

The solution

The solution is just a function to implement a comparison between an observable and an array of values, producing a promise that resolves if there is a match or rejects if not. It has the following signature:

It compares the values the observer produces with the provided array of values. It also checks for the observable completion or error if required.

Usage

I will illustrate the usage by an example of a Jasmine test for a timer generator service. The service generates an observable that makes a countdown then completes:

The test code is simple, it tries to match the generated sequence to an array of values using matchObservable().

Note the tick(10000). It is necessary to make the time pass for the observer timely behavior to happen (as in interval(1000)). Also, the resolving or rejection of the promise affects the local variable matchResult which is used to assert the match. This makes the asynchronous code to run sequentially and the test flow becomes "flat" making compositing different assertion steps straightforward.

If the observer to test does not use any specific timing, instead of tick(), use flush() or flushMicrotasks() to advance the asynchronous pending tasks.

The matchObservable function

The matchObservable() function subscribes the provided observer and, using the provided matcher, goes on comparing the observer emitted values with the values in the provided array.

As with any subscription, care must be taken about if the observer is hot or cold. If it’s hot, make sure you call matchObservable() before the observer starts emitting the values you want to compare with the array. If it is cold, take care about the possible side-effects caused by the repeated emission of the observer values. You can read more about hot and cold observables on the article by Ben Lesh.

A subscription and the corresponding unsubscribe are made within the function. And since we’re dealing a lot with asynchronous code (the promise and the observable) special care has been taken. This is concentrated on this helper function:

The unsubscribe() is only made on the next tick (accomplished with setTimeout() because, in some circumstances, some subscription handlers are run before the obs$.subscribe() returns and stores the value on the local variable subs. In those cases and if this function is called on that first call of the handler, the variable subs will be null at the time finalize() is called.

Also, when resolving or rejecting, the local variable expectedStep must be invalidated and this way will guard any handler to run significant code after the promise is resolved or rejected. The guard is necessary because another handler can be called in the same tick that the resolving/rejecting handler, but before the unsubscribe takes place.

“Next” handler

Using a provided matcher, the “next” handler goes on comparing the observer emitted values to the provided array of values. Rejects the promise if finding any inconsistency or resolves when finishes comparing the array of values.

“Error” and “Complete” handlers

These handlers are really simple and similar, deciding to resolve or reject the promise.

Note that with expectComplete and expectError both false, the returned promise is resolved as soon as the values array is matched. The remainder of the observer behavior is not observed. If both flags are true, the function matches either a complete or an error.

The full code is on a GitHub repository. It is also on npm.

Have fun!

PS: This article is also published on my blog.

Written by

Frontend developer. Lego crazy. Photography lover. Always learning. Remote worker.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store