Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TaskScheduler.FromCurrentSynchronizationContext - how to use WPF dispatcher thread when unit testing

I've got code in a ViewModel that calls a service through a task. When the task finishes, it'll populate an ObservableCollection. The problem is that it's waiting on the task to finish by using the ContinueWith method and providing TaskScheduler.FromCurrentSynchronizationContext as task scheduler, so that the OC gets updated on the UI thread.

So far so good, but when it comes to unit testing, it throws an exception saying that "the current SynchronizationContext may not be used as a TaskScheduler." If I use a mock SynchronizationContext on the unit test, then the ObservableCollection throws an error because it's being updated based from the dispatcher thread.

Is there any way to work around this?

Thanks.

like image 270
Alberto Avatar asked Nov 10 '11 10:11

Alberto


1 Answers

It's not exactly easy, but it's not really that hard either. What you need to do is a spin up a worker Thread that's setup as STA and you start up the Dispatcher runtime on it. Once you have that worker sitting there you can dispatch work to it from the unit test threads which are, obviously, not initialized for this kind of work. So, first, here's how you startup the dispatcher thread in your test setup:

this.dispatcherThread = new Thread(() =>
{
   // This is here just to force the dispatcher infrastructure to be setup on this thread
   Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
   {
       Trace.WriteLine("Dispatcher worker thread started.");
   }));

   // Run the dispatcher so it starts processing the message loop
   Dispatcher.Run();
});

this.dispatcherThread.SetApartmentState(ApartmentState.STA);
this.dispatcherThread.IsBackground = true;
this.dispatcherThread.Start();

Now, if you want to cleanly shut that thread down in your test cleanup, which I recommend you do, you simply do the following:

Dispatcher.FromThread(this.dispatcherThread).InvokeShutdown();

So, all that infrastructure stuff out of the way, here's all you need to do in your test to execute on that thread.

public void MyTestMethod
{
    // Kick the test off on the dispatcher worker thread synchronously which will block until the work is competed
    Dispatcher.FromThread(this.dispatcherThread).Invoke(new Action(() =>
    {
        // FromCurrentSynchronizationContext will now resolve to the dispatcher thread here
    }));
}
like image 143
Drew Marsh Avatar answered Sep 25 '22 20:09

Drew Marsh