views:

1974

answers:

6

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.

A: 

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.

Andrew Shepherd
A: 

If your goal is to avoid errors when accessing DependencyObjects, I suggest that, rather than playing with threads and Dispatcher explicitly, you simply make sure that your tests run in a (single) STAThread thread.

This may or may not suit your needs, for me at least it has always been enough for testing anything DependencyObject/WPF-related.

If you wish to try this, I can point you to several ways to do this :

  • If you use NUnit >= 2.5.0, there is a [RequiresSTA] attribute that can target test methods or classes. Beware though if you use an integrated test runner, as for example the R#4.5 NUnit runner seems to be based on an older version of NUnit and cannot use this attribute.
  • With older NUnit versions, you can set NUnit to use a [STAThread] thread with a config file, see for example this blog post by Chris Headgate.
  • Finally, the same blog post has a fallback method (which I've successfully used in the past) for creating your own [STAThread] thread to run your test on.
Thomas Dufour
+5  A: 

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: http://blogs.msdn.com/dancre/archive/2006/07/26/679870.aspx

Hope this helps! Stewart

StewartArmbrecht
Yeah, just came back to update this question with how I did it in the end. I read the same post I think!
Chris Shepherd
+8  A: 

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).

jbe
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.
Patrick Linskey
A: 

Creating a DipatcherFrame worked great for me:

    [TestMethod]
    public void Search_for_item_returns_one_result()
    {
        const string searchText = "test";

        var searchService = CreateSearchServiceWithExpectedResults(searchText, 1);

        const int expectedTotal = 1;

        var eventAggregator = new SimpleEventAggregator();

        var searchViewModel = new SearchViewModel(searchService, 10, eventAggregator) { SearchText = searchText };

        var signal = new AutoResetEvent(false);

        var frame = new DispatcherFrame();

        // set the event to signal the frame
        eventAggregator.Subscribe(new ProgressCompleteEvent(), () =>
                                                                   {
                                                                       signal.Set();
                                                                       frame.Continue = false;
                                                                   });

        searchViewModel.Search(); // dispather call happening here

        Dispatcher.PushFrame(frame);

        signal.WaitOne();

        Assert.AreEqual(expectedTotal, searchViewModel.TotalFound);
    }
Jon Dalberg
+2  A: 

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

Orion Edwards