Using the WPF Dispatcher in unit tests

Viewed 39703

I'm having trouble getting the Dispatcher to run a delegate I'm passing to it when unit testing. Everything works fine when I'm running the program, but, during a unit test the following code will not run:

this.Dispatcher.BeginInvoke(new ThreadStart(delegate
{
    this.Users.Clear();

    foreach (User user in e.Results)
    {
        this.Users.Add(user);
    }
}), DispatcherPriority.Normal, null);

I have this code in my viewmodel base class to get a Dispatcher:

if (Application.Current != null)
{
    this.Dispatcher = Application.Current.Dispatcher;
}
else
{
    this.Dispatcher = Dispatcher.CurrentDispatcher;
}

Is there something I need to do to initialise the Dispatcher for unit tests? The Dispatcher never runs the code in the delegate.

16 Answers

Winforms has a very easy and WPF compatible solution.

From your unit test project, reference System.Windows.Forms.

From your unit test when you want to wait for dispatcher events to finish processing, call

        System.Windows.Forms.Application.DoEvents();

If you have a background thread that keeps adding Invokes to the dispatcher queue, then you'll need to have some sort of test and keep calling DoEvents until the background some other testable condition is met

        while (vm.IsBusy)
        {
            System.Windows.Forms.Application.DoEvents();
        }

How about running the test on a dedicated thread with Dispatcher support?

    void RunTestWithDispatcher(Action testAction)
    {
        var thread = new Thread(() =>
        {
            var operation = Dispatcher.CurrentDispatcher.BeginInvoke(testAction);

            operation.Completed += (s, e) =>
            {
                // Dispatcher finishes queued tasks before shuts down at idle priority (important for TransientEventTest)
                Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.ApplicationIdle);
            };

            Dispatcher.Run();
        });

        thread.IsBackground = true;
        thread.TrySetApartmentState(ApartmentState.STA);
        thread.Start();
        thread.Join();
    }

I'm late but this is how I do it:

public static void RunMessageLoop(Func<Task> action)
{
  var originalContext = SynchronizationContext.Current;
  Exception exception = null;
  try
  {
    SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());

    action.Invoke().ContinueWith(t =>
    {
      exception = t.Exception;
    }, TaskContinuationOptions.OnlyOnFaulted).ContinueWith(t => Dispatcher.ExitAllFrames(),
      TaskScheduler.FromCurrentSynchronizationContext());

    Dispatcher.Run();
  }
  finally
  {
    SynchronizationContext.SetSynchronizationContext(originalContext);
  }
  if (exception != null) throw exception;
}

Simplest way I found is to just add a property like this to any ViewModel that needs to use the Dispatcher:

public static Dispatcher Dispatcher => Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;

That way it works both in the application and when running unit tests.

I only had to use it in a few places in my entire application so I didn't mind repeating myself a bit.

It's a bit old post, BeginInvoke is not a preferable option today. I was looking for a solution for mocking and had't found anything for InvokeAsync:

await App.Current.Dispatcher.InvokeAsync(() => something );

I've added new Class called Dispatcher, implementing IDispatcher, then inject into viewModel constructor:

public class Dispatcher : IDispatcher
{
    public async Task DispatchAsync(Action action)
    {
        await App.Current.Dispatcher.InvokeAsync(action);
    }
}
public interface IDispatcher
    {
        Task DispatchAsync(Action action);
    }

Then in test I've injected MockDispatcher into viewModel in constructor:

internal class MockDispatcher : IDispatcher
    {
        public async Task DispatchAsync(Action action)
        {
            await Task.Run(action);
        }
    }

Use in the view model:

await m_dispatcher.DispatchAsync(() => something);
Related