Writing tests of asynchronous code can be very tricky. You often have to resort to calling Thread.Sleep or Task.Delay within an asynchronous callback so you can control the timing and assert what you need to assert.
For the most part, these are ugly hacks. What you really want is a way to control execution timing with fine grained control. You need a device like Kirby's golden watch.
Here's the good news. When you use Reactive Extensions (Rx), you have such a device at your disposal! Try not to get into too much trouble with it.
In the past, I've written how Rx can reduce the cognitive load of asynchronous code through a declarative model. Rather than attempt to orchestrate all the interactions that must happen asynchronously at the right time, you simply describe the operations that need to happen and Reactive Extensions orchestrates everything for you.
This nearly eliminates race conditions and deadlocks while also reducing the cognitive load and potential for mistakes when writing asynchronous code.
Those are all amazing benefits of this approach, yet those aren't even my favorite thing about Reactive Extensions. My favorite thing is how the abstraction allows me to bend time itself to my will when writing unit tests. FEEL THE POWER!
Everything in Rx is scheduled using schedulers. Schedulers are classes that implement the IScheduler interface. This simple, but powerful, interface contains a Now property as well as three Schedule methods for scheduling actions to be run.
Control Time with the The TestScheduler
Rx provides the TestScheduler class (available in the Rx-Testing NuGet package) to give you absolute control over scheduling. This makes it possible to write deterministic repeatable unit tests.
Unfortunately, it's a bit of a pain to use as-is which is why Paul Betts took it upon himself to write some useful TestScheduler extension methods available in the reactiveui-testing NuGet package. This library provides the OnNextAt method. We'll use this to create an observable that provides values at specified times.
The following test demonstrates how we can use the TestScheduler.
public void SchedulerDemo()
var sched = new TestScheduler();
var subject = sched.CreateColdObservable(
sched.OnNextAt(100, "m"), // Provides "m" at 100 ms
sched.OnNextAt(200, "o"), // Provides "o" at 200 ms
sched.OnNextAt(300, "r"), // Provides "r" at 300 ms
sched.OnNextAt(400, "k") // Provides "k" at 400 ms
string seenValue = null;
subject.Subscribe(value => seenValue = value);
Read more: You've been haacked