问题
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