5

RxJS Advanced Techniques: Testing Race Conditions Using RxJS Marbles

 3 years ago
source link: https://blog.nrwl.io/rxjs-advanced-techniques-testing-race-conditions-using-rxjs-marbles-53e7e789fba5
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

RxJS Advanced Techniques: Testing Race Conditions Using RxJS Marbles

Image for post
Image for post

Victor Savkin is a co-founder of nrwl.io. He was previously on the Angular core team at Google, and built the dependency injection, change detection, forms, and router modules

Building a web application involves coordinating multiple backends, web workers, and UI components, all of which update the application’s state concurrently. This makes it easy to introduce bugs due to race conditions. In this article I will show an example of such a bug, how to expose it in unit tests using RxJS marbles, and, finally, how to fix it.

I’ll use Angular and RxJS, but everything I’ll talk about is true for any web application, regardless of the used framework.

Problem

Let’s look at MovieShowingsComponent. It’s a simple component displaying movie showings. When the user selects a movie, the component will immediately display the selected title, and then, once it receives the response from the backend, will display the corresponding showings.

This component has a race condition. To see where, let’s imagine the following scenario. Say the user starts by selecting ‘After the Storm’, and then selects ‘Paterson’. This is what it will look like:

Image for post
Image for post

We are making one assumption here: the response for ‘After the Storm’ will come first. But what if this isn’t the case? What will happen if the response for ‘Paterson’ comes first?

Image for post
Image for post

The application will say ‘Paterson’, but will display the showings for ‘After the Storm’ — it is broken.

Before we start fixing it, let’s write a unit test exposing this race condition. There are, of course, many ways to do it. But since we are using RxJS, we will use marbles — one of the most powerful and underused ways of testing concurrent code.

Marbles

To use marble we need to install the jasmine-marbles library.

npm install — save-dev jasmine-marbles

Before writing a test for the component, let’s look at how marble testing works in general by testing the concat operator.

Here we create two observables one$ and two$, using the cold helper provided by jasmine-marbles (If you aren’t familiar with hot and cold, read this post by Ben Lesh.)

Next, we use the concat operator to get the result observable, which we compare against the expected one.

Marbles are a domain specific language for defining RxJS observables. Using it we can define when our observables emit values, when they are idle, when they error, when they get subscribed to, and when they complete. In our test we define two observables, one of which (’x-x|’) emits an ‘x’, then waits for 10 milliseconds, then emits another ‘x’, and then completes. And the other one waits for 10 milliseconds before emitting a ‘y’.

Often emitting single-letter strings isn’t sufficient. The cold helper provides a way to map them to other objects, like this:

As with many DSLs, we use marbles to improve the readability of our tests. Marbles do an excellent job at it — we can see what a test does by just glancing at it.

If you want to learn more about testing, check out this video.

Testing Race Condition

Armed with this powerful tool, let’s write a unit test exposing the race condition.

Fixing the Race Condition

Let’s look at our component one more time.

Every time the user select a movie, we create a new independent observable. If the user clicks twice, we will have two observables, which are not synchronized in any way. This is the source of the problem.

Let’s change it by introducing an observable of all getShowings invocations.

Next, let’s map it to a list of showings.

By doing this we replaced a collection of independent observables with a single observable of observables, which we can apply synchronization operators to. And that’s what switchMap is.

The switchMap operator only subscribes to the latest invocation of backend.getShowings. If another invocation happens, it will unsubscribe from the one before.

Image for post
Image for post

With this change our test will pass.

Source Code

You can find the source code in this repo. Note ”skipLibCheck”: true in tsconfig.spec.json.

Summary

In this article we have looked at an example of a bug caused by a race condition. We used marbles, a powerful way to test async code, to expose this bug in a unit test. We then fixed the bug by refactoring our code to use a single observable of observables, which we applied switchMap to.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK