Using the WPF Dispatcher in unit tests

38,390

Solution 1

By using the Visual Studio Unit Test Framework you don’t need to initialize the Dispatcher yourself. You are absolutely right, that the Dispatcher doesn’t automatically process its queue.

You can write a simple helper method “DispatcherUtil.DoEvents()” which tells the Dispatcher to process its queue.

C# Code:

public static class DispatcherUtil
{
    [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public static void DoEvents()
    {
        DispatcherFrame frame = new DispatcherFrame();
        Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
            new DispatcherOperationCallback(ExitFrame), frame);
        Dispatcher.PushFrame(frame);
    }

    private static object ExitFrame(object frame)
    {
        ((DispatcherFrame)frame).Continue = false;
        return null;
    }
}

You find this class too in the WPF Application Framework (WAF).

Solution 2

We've solved this issue by simply mocking out the dispatcher behind an interface, and pulling in the interface from our IOC container. Here's the interface:

public interface IDispatcher
{
    void Dispatch( Delegate method, params object[] args );
}

Here's the concrete implementation registered in the IOC container for the real app

[Export(typeof(IDispatcher))]
public class ApplicationDispatcher : IDispatcher
{
    public void Dispatch( Delegate method, params object[] args )
    { UnderlyingDispatcher.BeginInvoke(method, args); }

    // -----

    Dispatcher UnderlyingDispatcher
    {
        get
        {
            if( App.Current == null )
                throw new InvalidOperationException("You must call this method from within a running WPF application!");

            if( App.Current.Dispatcher == null )
                throw new InvalidOperationException("You must call this method from within a running WPF application with an active dispatcher!");

            return App.Current.Dispatcher;
        }
    }
}

And here's a mock one that we supply to the code during unit tests:

public class MockDispatcher : IDispatcher
{
    public void Dispatch(Delegate method, params object[] args)
    { method.DynamicInvoke(args); }
}

We also have a variant of the MockDispatcher which executes delegates in a background thread, but it's not neccessary most of the time

Solution 3

You can unit test using a dispatcher, you just need to use the DispatcherFrame. Here is an example of one of my unit tests that uses the DispatcherFrame to force the dispatcher queue to execute.

[TestMethod]
public void DomainCollection_AddDomainObjectFromWorkerThread()
{
 Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
 DispatcherFrame frame = new DispatcherFrame();
 IDomainCollectionMetaData domainCollectionMetaData = this.GenerateIDomainCollectionMetaData();
 IDomainObject parentDomainObject = MockRepository.GenerateMock<IDomainObject>();
 DomainCollection sut = new DomainCollection(dispatcher, domainCollectionMetaData, parentDomainObject);

 IDomainObject domainObject = MockRepository.GenerateMock<IDomainObject>();

 sut.SetAsLoaded();
 bool raisedCollectionChanged = false;
 sut.ObservableCollection.CollectionChanged += delegate(object sender, NotifyCollectionChangedEventArgs e)
 {
  raisedCollectionChanged = true;
  Assert.IsTrue(e.Action == NotifyCollectionChangedAction.Add, "The action was not add.");
  Assert.IsTrue(e.NewStartingIndex == 0, "NewStartingIndex was not 0.");
  Assert.IsTrue(e.NewItems[0] == domainObject, "NewItems not include added domain object.");
  Assert.IsTrue(e.OldItems == null, "OldItems was not null.");
  Assert.IsTrue(e.OldStartingIndex == -1, "OldStartingIndex was not -1.");
  frame.Continue = false;
 };

 WorkerDelegate worker = new WorkerDelegate(delegate(DomainCollection domainCollection)
  {
   domainCollection.Add(domainObject);
  });
 IAsyncResult ar = worker.BeginInvoke(sut, null, null);
 worker.EndInvoke(ar);
 Dispatcher.PushFrame(frame);
 Assert.IsTrue(raisedCollectionChanged, "CollectionChanged event not raised.");
}

I found out about it here.

Solution 4

I solved this problem by creating a new Application in my unit test setup.

Then any class under test which access to Application.Current.Dispatcher will find a dispatcher.

Because only one Application is allowed in an AppDomain I used the AssemblyInitialize and put it into its own class ApplicationInitializer.

[TestClass]
public class ApplicationInitializer
{
    [AssemblyInitialize]
    public static void AssemblyInitialize(TestContext context)
    {
        var waitForApplicationRun = new TaskCompletionSource<bool>();
        Task.Run(() =>
        {
            var application = new Application();
            application.Startup += (s, e) => { waitForApplicationRun.SetResult(true); };
            application.Run();
        });
        waitForApplicationRun.Task.Wait();        
    }
    [AssemblyCleanup]
    public static void AssemblyCleanup()
    {
        Application.Current.Dispatcher.Invoke(Application.Current.Shutdown);
    }
}
[TestClass]
public class MyTestClass
{
    [TestMethod]
    public void MyTestMethod()
    {
        // implementation can access Application.Current.Dispatcher
    }
}

Solution 5

When you call Dispatcher.BeginInvoke, you are instructing the dispatcher to run the delegates on its thread when the thread is idle.

When running unit tests, the main thread will never be idle. It will run all of the tests then terminate.

To make this aspect unit testable you will have to change the underlying design so that it isn't using the main thread's dispatcher. Another alternative is to utilise the System.ComponentModel.BackgroundWorker to modify the users on a different thread. (This is just an example, it might be innappropriate depending upon the context).


Edit (5 months later) I wrote this answer while unaware of the DispatcherFrame. I'm quite happy to have been wrong on this one - DispatcherFrame has turned out to be extremely useful.

Share:
38,390
Chris Shepherd
Author by

Chris Shepherd

Updated on October 01, 2021

Comments

  • Chris Shepherd
    Chris Shepherd over 2 years

    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.

  • Chris Shepherd
    Chris Shepherd over 14 years
    Yeah, just came back to update this question with how I did it in the end. I read the same post I think!
  • Patrick Linskey
    Patrick Linskey over 13 years
    I prefer this answer to the accepted answer, as this solution can run in a sequentially-authored test case, whereas the accepted answer requires that the test code is written in a callback-oriented approach.
  • dodgy_coder
    dodgy_coder about 12 years
    This isn't working for me unfortunately. This method is also documented here for those interested: MSDN DispatcherFrame 'DoEvents' Example
  • dodgy_coder
    dodgy_coder about 12 years
    Ignore my last comments - it works fine and is a good workaround to this common issue when testing WPF view models.
  • EngineerSpock
    EngineerSpock over 9 years
    Am I right that this method shoud be called every time a system under test have to deal with Dispatcher? Or this method has to be called only once per unit-test session?
  • komizo
    komizo about 9 years
    how to mock DispatcherInvoke method?
  • Wim Coenen
    Wim Coenen over 7 years
    Your DoEvents will risk executing invokes that were scheduled by other unit tests, resulting in failures which are very difficult to debug :-( I found this post because somebody added a verbatim copy of the DispatcherUtil sample code to our unit tests and caused that very issue. I believe that hiding the dispatcher behind an interface as suggested by @OrionEdwards is a better approach, though I would use an implementation with an actual queue and a method for explicit dequeuing in the unit tests. If I get around to implementing it I'll add or edit an answer here.
  • Doctor Jones
    Doctor Jones over 6 years
    It's worth noting that requiring a real dispatcher is a bit of a red flag. Your unit tests should be testing each part of your code in isolation, and mocking up all other elements. @Orion's answer is more inline with correct unit testing best practices. You shouldn't need to use a real dispatcher in your unit tests, for the same reason you wouldn't use a real database connection, real http requests, etc.
  • Doctor Jones
    Doctor Jones over 6 years
    @lukaszk, depending on your mocking framework, you would setup the Invoke method on your mock to actually run the delegate passed into it (if that were the behaviour that you require). You don't necessarily need to run that delegate, I have some tests where I just verify that the correct delegate was passed to the mock.
  • ManIkWeet
    ManIkWeet about 6 years
    Dispatcher.PushFrame(frame); uses Dispatcher.CurrentDispatcher internally... So this wouldn't work.
  • Denise Skidmore
    Denise Skidmore over 4 years
    But even if you use Dispatcher.CurrentDispatcher, you have to get the Dispatcher to run, and not continue your test until it is finished.
  • Denise Skidmore
    Denise Skidmore over 4 years
    For very low level unit testing this works, but sometimes you need to test your functions with background threads.
  • Shahin Dohan
    Shahin Dohan over 4 years
    @DeniseSkidmore When running the test, the CurrentDispatcher simply becomes the thread that the test is running on, otherwise the method your testing would fail with a null reference exception because Application.Current is null. This should have nothing to do with how the test works.
  • aydjay
    aydjay over 4 years
    I like this alot!
  • Denise Skidmore
    Denise Skidmore about 4 years
    For a single threaded test, that is true. If your unit test isn't low enough level, and spawns another thread which then uses Dispatcher.CurrentDispatcher.Invoke, you need to allow that to run in your primary test thread.
  • user2315856
    user2315856 over 3 years
    For those using Moq, here is what worked for me: ` var mockDispatcher = new Mock<IDispatcher>(); mockDispatcher.Setup(dispatcher => dispatcher.Invoke(It.IsAny<Action>())).Callback<Action>(acti‌​on => action());`