Getting Started with Marble Testing

Todd Leininger Testing, Tutorial Leave a Comment

When working with RxJS observables, it can get a little tricky to unit test. Sometimes it can be hard to get insight into what is going on with the observable in the test. I’ve personally been frustrated numerous times by trying to test my observables with the subscribe and assert method. This is where marble testing can make testing observables easier.

The main advantage of using marble testing is the marble diagrams. With the diagrams, we have a visual representation of our observables and subscriptions so we can see the interactions between the two. Once the basics are understood, you should be able to get observable tests running in no time.

In this post, I will show you how to get started using Marble testing with an example. By the end, you should have enough information to get started on your journey testing observables. My examples are based on an Angular application using Jasmine, but these can also be applied to different testing frameworks for React and other applications.

Setup

For this article, I will use jasmine-marbles to add marble testing to my Jasmine based unit tests. Another option would be to use rxjs-marbles, which can be used with any test framework.

First, you will want to open up a terminal and run this npm install command.

$ npm install jasmine-marbles --save-dev

Now that you have installed jasmine-marbles as a dev dependency, we are ready to start setting up your tests. Before we do that let’s take a more specific look at what marble testing is.

Marbles Syntax

Take a look at the syntax used in marble testing.

  • '-' Time: One '-' is 10 frames of time passage. One frame is equal to one virtual millisecond.
  • ' ' Whitespace: Horizontal whitespace can be used to help line up the marble diagrams to help with visualization.
  • '|' Complete: This marks the successful completion of the observable.
  • '#' Error: This marks the termination of the observable by throwing an error.
  • '[a-z0-9]' Any alphanumeric characters: Represents the value being emitted. It can also map to an object.
  • '()' Sync group: Use this when multiple events need to be in the same frame.
  • '^' Subscription point: For hot observables. This shows where the observable will be subscribed.
  • '!' Unsubscribe point: These shows were the observable will be unsubscribed.
See Also:  React, The Extras.

Marbles API

Now, let’s look at the two APIs that we will be focusing on for our marbles example.

  • cold(marbleDiagram: string, values?: object, error?: any) – creates a “cold” observable. The subscription starts (the zero frame) when the test begins.
  • hot(marbleDiagram: string, values?: object, error?: any) – creates a “hot” observable. The observable behaves as if it is already running when the test begins.

Cold Examples

Here we are going to build some cold marble tests.

describe('cold marble testing', () => {
  const testScheduler = new TestScheduler((actual, expected) => actual === expected);

  testScheduler.run(({ cold, expectObservable, expectSubscriptions }) => {
    const source = cold('-a--b--c---|');
    const sub1 = '       -----------!';
    const expect1 = '    -a--b--c---|';
    const sub2 = '       -----!';
    const expect2 = '    -a--b-';

    it ('should have correct values', () => {
      expectObservable(source, sub1).toBe(expect1);
      expectObservable(source, sub2).toBe(expect2);
      expectSubscriptions(source.subscriptions).toBe([sub1, sub2]);
    });
  });
});

To test the observables we are using the TestScheduler provided by rxjs/testing. We have three marble diagrams: one for the cold observable, one for the subscription, and one for the expected results.

The observable source starts at frame 0, and after the first frame will emit a. After two more frames it will emit b, and two more frames c will be emitted. The first subscription, sub1, subscribes at frame 0 and unsubscribes at frame 11.

In our it we are passing in source and sub1 to the expectObservable helper and expecting to get back the result of expect1, which is everything emitted from the source observable.

Still using source, let’s test sub2. This subscription once again starts at frame 0 but unsubscribes at frame 5. This will only return the results shown in expect2.

The last thing tested, is verification of the observable’s subscription list using expectSubscriptions. This tests that both subscriptions were in the observables list of subscriptions.

See Also:  Avoiding Test-Driven Development?

Hot Examples

Hot observable tests look the same as the cold. The only difference being that the hot observable acts an already running observable.

describe('hot marble testing', () => {
  const testScheduler = new TestScheduler((actual, expected) => actual === expected);
  testScheduler.run(({ hot, expectObservable, expectSubscriptions }) => {
    const source = hot('--a--b----c--d-----e--');
    const sub1 = '      --^---------!';
    const expect1 = '   --a--b----c--';
    const sub2 = '      ---------^--------!';
    const expect2 = '   ----------c--d-----';
    const sub3 = '      -------------^-----!';
    const expect3 = '   -------------d------';

    it ('should have correct values based on subscription', () => {
      expectObservable(source, sub1).toBe(expect1);
      expectObservable(source, sub2).toBe(expect2);
      expectObservable(source, sub3).toBe(expect3);
      expectSubscriptions(source.subscriptions).toBe([sub1, sub2, sub3]);
    });
  });
});

These tests are structured the same as the cold tests above. We have the source observable and three different subscriptions and expected results.

Each subscription is subscribing to the observable at a different point. sub1 subscribes to source at frame 3 and unsubscribes at frame 13. This results in the values a, b, c at their respective frames. With the other two examples, we move the subscription and unsubscription frames around that will result in different results for each subscription. We are then also verifying that the source has the correct subscriptions in its subscription list.

Final Thoughts

I hope with the above descriptions and examples you have enough to get started on your journey testing observables in your Angular applications. The above is just the beginning of what you can do with marble testing.

For more information on marble testing, I recommend the RxJS documentation on the subject, along with the documentation for both jasmine-marbles and rxjs-marbles.

Related Posts

0 0 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments