Testing RxJava with executable marble diagrams
25 Mar 2013Reactive Extensions (Rx) from the .Net world are observable
sequences. In particular they are push based sequences, in contrast
to the pull based seq api in Clojure. Furthermore you can subscribe
to and
unsubscribe from observable sequences. This also makes observable
sequences a nice candidate to replace futures and callbacks in
asynchronous code.
For more information see any of the following resources:
- Rx (Reactive Extensions) .Net Source
- Erik Meijer (Rx creator) explains Rx video
- Intro to Rx Free html ebook
In particular the "Intro to Rx" book is a great resource. Every other example will have you wishing to be able to use Rx in Clojure. Luckily Netflix recently open sourced their Java variation of Rx called RxJava.
- RxJava: Functional Reactive Programming on the JVM Neflix/RxJava
- RxJava at Netflix blogpost / slideshow
Marble diagrams
Both the Intro to Rx book and the RxJava javadoc use the concept of marble diagrams. A marble diagram shows how different operators work and how sequences can be combined. The following diagram is the example from RxJava: Source: Neflix/RxJava
The following is the same diagram in Clojure code:
(deftest flip-example (is (marble [:src [ 1 2 3 4 5 6 | ] :expr (.map :src (fn [value] (if (= value 4) (throw (Exception. "DemoFail on 4")) (* -1 value)))) :out [-1 -2 -3 X]])))Source: test/reactive_marbles/rxjava_test.clj
The benefit of the diagram being in code is that it can be executed. The reactive-marbles project contains most of the example marble diagrams from RxJava in Clojure, as code and executable as tests.
Found bugs
Running reactive-marbles gives the following output:
lein test reactive-marbles.rxjava-test FAIL in (take-test) (rxjava_test.clj:139) Marble diagram test [:src [1 2 3 4 5 |] :expr (Observable/take :src 2) :out [1 2 |]] expected: [1 2 |] actual: [1 2 -] FAIL in (to-sorted-list) (rxjava_test.clj:163) Marble diagram test [:src [2 5 1 6 3 4 |] :expr (Observable/toSortedList :src) :out [- - - - - - [1 2 3 4 5 6] |]] expected: [- - - - - - [1 2 3 4 5 6] |] actual: [- - - - - - [1 1 2 2 3 3 4 4 5 5 6 6] |] FAIL in (list-test) (rxjava_test.clj:153) Marble diagram test [:src [1 2 3 4 5 6 |] :expr (Observable/toList :src) :out [- - - - - - [1 2 3 4 5 6] |]] expected: [- - - - - - [1 2 3 4 5 6] |] actual: [- - - - - - [1 1 2 2 3 3 4 4 5 5 6 6] |] FAIL in (take-while-test) (rxjava_test.clj:145) Marble diagram test [:src [1 2 3 4 5 |] :expr (Observable/takeWhile :src (fn [i] (< i 3))) :out [1 2 |]] expected: [1 2 |] actual: [1 2 -] Ran 33 tests containing 33 assertions. 4 failures, 0 errors. Tests failed.
The 4 failed test cases are the result of 2 bugs in RxJava 0.6.1. See rxjava_test.clj for details on the found bugs and links to the bug reports.
Marble diagrams are useful to see the difference between operators and to get an intuition about their behavior. As code they serve as both documentation and tests. However marble diagrams do not properly show the importance of subscriptions and how observable sequences behave when they produce values at variable rates. Again, the Intro to Rx book is a great resource to learn about that.
Code is on github: thegeez/reactive-marbles