On the perils of traversing parallel universes

Or, why you shouldn't mix Rx and TPL if you want to write testable code

Published on 01 February 2016

Despite taking some poetic license with the title of this post, the dramatics are not without merit. I have spent a significant portion of the last three days trying to write a series of tests around some asynchronous code to prove it managed - or, more specifically, limited - concurrency as intended. This code, while mainly Rx based, made calls to TPL methods and needed to wait, without blocking, for a result to be returned prior to allowing subsequent calls to be made. In short it mixed Rx and TPL to implement a multi-procuder / single consumer concurrency pattern, and this mix proved to be the source of much (much, much!) frustration.

While I don't necessarily believe that threading should be avoided in unit tests I do, as much as possible, endeavour to keep tests synchronous. Obviously this can be tricky when you're specifically testing concurrency but, fortunately, this has become immeasurably easier since Rx introduced testing in virtual time via the TestScheduler.

So, with this magical mechanism for manipulating the motion of time in my mitts, I proceeded to write the test ShouldOnlySendASingleCommandAtATime... which promptly failed. And I'm not talking the good, red-green kinda failure. Nooo, this was an old school "I've written the code, better make sure it works... oh, that's weird!" kinda failure.

Assertions on calls to faked objects were failing and, after a lot of digging I finally found out why: Despite diligently injecting and using a TestScheduler through my Rx code and mocking calls to TPL code such that they returned TaskCompletionSource<T> instances, I was still seeing my unit tests start a second worker thread!

Weird indeed.

After a lot of hacking around with my code and other "computer programming" type activities, I finally happened upon this curiously titled question. The question very closely reflected what I was trying to achieve and was fortunately followed by an incredibly detailed answer by James World. Of particular note was this paragraph:

One particular pain point of Rx that leaves many testers scratching their heads, is the fact that the TPL -> Rx family of conversions introduce concurrency. e.g. ToObservable, SelectMany's overload accepting Task etc. don't provide overloads with a scheduler and insidiously force you off the TestScheduler thread, even if mocking with TCS

Bingo! Exactly the issue I was experiencing. James also provided a link to the following bug report for Rx validating his answer and vindicating my confusion. This epiphany lead me to rewrite the interfaces to my dependencies such that they were Rx rather than TPL based. After which, lo and behold, my unit tests started passing.

Phew!

In conclusion, I guess the physicists are right: If you want to move between parallel universes, be prepared to fall into a black hole!