Correct method for using the WPF Dispatcher in unit tests

生来就可爱ヽ(ⅴ<●) 提交于 2019-12-23 08:56:32

问题


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.


回答1:


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.




回答2:


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



来源:https://stackoverflow.com/questions/9336165/correct-method-for-using-the-wpf-dispatcher-in-unit-tests

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!