Angular Marble Testing: A Brief Introduction

Working with Angular implies a wide use of reactive programming, that is, programming with asynchronous data streams. Angular components often operate with several observable streams that have overlapping sequences of values and errors. Testing such scenarios using common methods is often complicated. RxJS Marble Testing is much more helpful in these cases.

In this article, we’ll try to explain what is marble testing and how it works.

  1. What is marble testing?
  2. ‘Jasmine-marbles’ library
  3. Example

What is marble testing?

Marble testing allows you to test asynchronous RxJS code synchronously and step-by-step with the help of RxJS TestScheduler test utility and using virtual time steps.

There are also marble diagrams which demonstrate what is happening with a particular operation in the observable stream.

angular marble testing

ASCII marble diagrams are an alternative way to describe the observable stream. For instance, ASCII diagram a–bc—d–#–| corresponds to the marble diagram on the image above.

A marble diagram in TestScheduler is a string of characters that represents events which occur during the virtual time. Time progresses through ‘frames’. The first character represents a zero ‘frame’ or the beginning of time.

  • “-” — 10 ‘frames’ of time passing
  • “|” — the successful completion of the observable stream. Corresponds to the complete() method.
  • “#” — the error completion of the observable stream. Corresponds to the error() method.
  • “a” (or any other alphanumeric character) — a value emitted by the observable stream. Corresponds to the next() method.
  • “()” — a grouping of several events that should occur synchronously in one ‘frame’. Allows you to group the emitted value with the end of stream or an error.
  • “^” — a subscription point (only for hot observables). It’s a ‘zero frame’ for the observable, so each frame before ^ will be negative. Negative time may be needed during ‘ReplaySubjects’ testing.
  • “!” —  the end of a subscription point.

ASCII marble diagrams are used to create so-called hot and cold observable streams, which, in their turn, are used as mock-ups in test-waiting methods. Let’s discuss this next.

Jasmine-marbles library

‘Jasmine’ provides the ‘npm’ package ‘jasmine-marbles’. It is a library for `TestScheduler` that significantly simplifies marble testing. This package is not included in the Angular CLI test suite, so it must be installed separately: ‘$ npm install jasmine-marbles –save-dev’

‘Jasmine-marbles’ provides two methods for creating observables out of marble diagrams:

  • ‘cold (marbles: string, values ?: object, errors ?: any)’ – the subscription starts when the test begins
  • ‘hot (marbles: string, values ?: object, errors ?: any)’ – it’s already “running” when the test begins; the subscription starts with the “^” character.

ASCII marble diagram is passed to both methods as the first argument. The second argument is an optional object matching the characters in the diagram and their values. If this object is absent, the created observable will be emitting the characters from the diagram. If there is an error in the stream, it will be passed as the third argument.

For instance, ‘cold(‘–a–b–|’, {a: 10, b: ‘hello’})’ will create a cold observable stream that will emit value 10 at 30ms, value ‘hello’ at 60ms and will end at 90ms.

Thanks to the work of the test scheduler, the test is synchronous (`fakeAsync ()` is not used).

Example

Let’s take a look at a common example – a counter component.  

We have the following requirements to the component:

  1. The component must have two buttons (‘Up’ and ‘Down’) and a counter.
  2. The counter should start at 0.
  3. Clicking the ‘Up’ button should add 1 to the counter; clicking the ‘Down’ button should subtract 1 from the counter.

Now, let’s create a component test considering the above requirements:

marble testing code

Next, we need to describe the logic behind the way the component works with the observable stream using marble diagrams:

  1. Stream of events after pressing the ‘Up’ button
  2. Stream of events after pressing the ‘Down’ button.
  3. The resulting stream.

Now, based on the marble diagrams, let’s create the observable streams using the ‘cold ()’ method from the `jasmine-marbles` library. We need this to verify whether the resulting observable stream of the counter corresponds to the observable streams of click events on the ‘Up’ and ‘Down’ buttons.

After that, we can implement a component code that will successfully pass the tests.

import { Component } from '@angular/core';
import { merge, Observable, Subject } from 'rxjs';
import { mapTo, scan, startWith } from 'rxjs/operators';

@Component({
 selector: 'app-counter',
 template: `
   <button (click)="up$.next()">Up</button>
   <button (click)="down$.next()">Down</button>
   <div class="counter">{{counter$ | async}}</div>
 `,
})
export class CounterComponent {
 counter$: Observable<number>;
 up$ = new Subject();
 down$ = new Subject();

 constructor() {
   const {up$, down$} = this;
   this.counter$ = this.getCounter({up$, down$});
 }

 getCounter({up$, down$}) {
   return merge(
     up$.pipe(mapTo(1)),
     down$.pipe(mapTo(-1))
   ).pipe(
     startWith(0),
     scan((x, y) => x + y)
   )
 }
}

To receive a whole code from this article, fill out the form below and click Send Download Link button.

Enter your name and email to download the file.

Conclusion

As mentioned, the described methodology is indeed very effective and it’s the best option for testing asynchronous RxJS code. It will help you easily test even the most complex networks of observable streams in various Angular components of the application.

Looking for professional app developers for your next project?

👍 Rating — 0 (0 votes)

Top