Before Redux Observable, I found testing asynchronous side effects (redux-thunk) very difficult. Our code was always messy, relying on weird mocks or tightly coupled to redux dispatcher. Moreover, it would always involve a callback or an await through every step. Something like this:
While this is brief and simple, it does not indicate when these actions were fired, just the order. If you add a timeout or debounce to your thunk, these tests become very complicated.
Whereas with Redux Observable, I can declare time, order, and various sequences all in the same test. Say we want to simulate a 300ms delay between request and success:
Notice, that the ‘a 300ms b’ declaration. The a represents the loading action at the first millisecond and the 300ms wait is just after.
Then at 302 ms, we have the b action which maps to the success action.
This marble syntax allows us to directly declare the behaviour and expectations of our observable over time, allowing us to clearly describe what is happening.
Redux observable is a separate running process that starts at the beginning of your application. It does not run on a middleware system like redux thunk. It is a set of observables merged together into a stream that listens to the actions dispatched by the store (dispatch). Similar to redux, every action dispatched triggers a series of processes.
Unlike redux, Redux observable uses a set of merged observables to process the actions all at once. Observables are lazy Push collections of multiple values, a representation of values over time. Some people call these streams. While this may be incorrect, it is similar in behaviour. Data flows through these observables from an input to an output.
In Redux Observable, these observables are called Epics - a function which takes a stream of actions and returns a stream of actions. These actions are then committed to the store. For every action one can return a set of actions (0 or more).
See a Todos example below:
Above, we continuously listen to the FETCH_TODOS_REQUEST action by filtering it out of the actions stream with the ofType operator.
Then, we use the switchMap operator to map to an inner observable (concat) which sends out multiple actions.
The switchMap operator allows us to cancel the previous inner observable if it is running, and then create a fresh one.
This is very useful for cancelling ajax requests.
Concat then allows us to first emit one action and then emit another delayed action. Notice, that both of these are inside a stream of, so one can add delays/timers/debouncing to this if needed. Any action that is an output of this Observable will be dispatched to the store and the cycle continues to whatever epic is listening.
Marble testing is a mechanism that allows us to test how observables behave over time. This allows one to declaratively test the values an observable emits over time.
I won’t cover marble testing in depth, but highlight its importance and advantages over testing promises, functions, callbacks.
If you want an indepth introduction, I highly recommend reading this article afterward.
A marble test is a comparison of one observables emitted values to another expected observables emitted values. These values are representated by ticks (millisecond length) in time like below and mapped characters:
The pipe ‘ | ’ means the observable completed at this millisecond. |
While it may be difficult to understand it all first, it’s definitely a lot more declarative and easier to debug than any other known asynchronous testing I’ve seen.
Marble testing makes TDD very easy and straightforward. Declare the marbles first then implement the solution in the epic.
Also RXJS is incredibly powerful and built for complex applications. With it, these one-line operators allow the following:
Give it a go and let me know how RXJS, Marble testing, and Redux observable work out for you!
Cheers!