9

I am trying to follow the suggestions from Using the WPF Dispatcher in unit tests in order to get my nUnit test to run.

When I write my unit test as below, it works:

[Test]
public void Data_Should_Contain_Items()
{
    DispatcherFrame frame = new DispatcherFrame();
        PropertyChangedEventHandler waitForModelHandler = delegate(object sender, PropertyChangedEventArgs e)
        {
          if (e.PropertyName == "Data")
          {
            frame.Continue = false;
          }
        };
    _myViewModel.PropertyChanged += waitForModelHandler;
    Dispatcher.PushFrame(frame);

    Assert.IsTrue(_myViewModel.Data.Count > 0, "Data item counts do not match");
}

However, if I try to use the suggestion of the DispatcherUtil, it does not work:

[Test]
public void Data_Should_Contain_Items()
{
    DispatcherUtil.DoEvents();
    Assert.IsTrue(_myViewModel.Data.Count > 0, "Data item counts do not match");
}

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;
    }
}

When I am using the DispatcherUtil, it looks like the call to ExitFrame happens too soon, before the data is ready.

Am I not using the DispatcherUtil correctly? It seems like a better method to use to handle the dispatcher rather then waiting for callbacks from the view model.

Community
  • 1
  • 1
Flack
  • 5,727
  • 14
  • 69
  • 104
  • What are you trying to test, just if the PropertyChangedEventHandler is invoked for property "Data"? If so why do you need to involve the dispatcher? I also don't using _myViewModel apart to attach the handler. – Phil Feb 18 '12 at 08:46
  • @Phil: When _myViewModel is instantiated, its constructor makes an asyn call. When that call completes, _myViewModel.Data should have some values. I am trying to test that Data is in fact populated, but the fact that Data is populated as a result of an asyn call is what is causing me some trouble. I would like to avoid having to listen to PropertyChanged events in any unit test that might have to deal with the Dispatcher. – Flack Feb 18 '12 at 23:49

2 Answers2

6

Since the dispatcher is problematic in unit tests, my solution would be to break your view-model's dependency on the dispatcher. I assume that currently you have hard coded references like:

Dispatcher.CurrentDispatcher.BeginInvoke(..

The dispatcher is an external dependency and shouldn't be part of your unit tests - we can assume the dispatcher works.

I would use dependency injection (either poor mans, Unity, etc). Create a suitable interface representing the dispatcher. Create a real implementation which wraps the real dispatcher. Create a fake implementation which uses Action.BeginInvoke. In the fake you record all IAsyncResults returned to calls to BeginInvoke.
Then have a helper method which would wait for all calls to completed which you can use in your test to wait for completion.

Or have a view model base class which does the same thing. Calls the dispatcher normally but can be directed to call a fake during tests.

Phil
  • 42,255
  • 9
  • 100
  • 100
3

I found an easy solution. Instead of processing the Frames Async in the Dispatcher, I added a synced method to the DispatcherUtil class. Calling this DoEventsSync()-method returns when all Frames were processed, i think this should help here:

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

        public static void DoEventsSync()
        {
            var frame = new DispatcherFrame();
            Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background,
                new DispatcherOperationCallback(ExitFrame), frame);
            Dispatcher.PushFrame(frame);
        }

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

Now simply use DispatcherUtil.DoEventsSync(); instead of DispatcherUtil.DoEvents(); in the Unit-Tests. You can be sure the Dispatcher processed everything, before the Unit-Tests continue. No callbacks need to be added for tests.

The confusing part is that DispatcherUtil.DoEvents(); really is a DispatcherUtil.DoEventsAsync(); because BeginInvoke(..) is an Async-Method

thewhiteambit
  • 1,365
  • 16
  • 31