在单元测试中使用WPF Dispatcher

Chr*_*erd 48 .net c# wpf unit-testing dispatcher

我无法让Dispatcher运行委托我在单元测试时传递给它.当我运行程序时,一切正常,但是,在单元测试期间,以下代码将无法运行:

this.Dispatcher.BeginInvoke(new ThreadStart(delegate
{
    this.Users.Clear();

    foreach (User user in e.Results)
    {
        this.Users.Add(user);
    }
}), DispatcherPriority.Normal, null);
Run Code Online (Sandbox Code Playgroud)

我在我的viewmodel基类中有这个代码来获取Dispatcher:

if (Application.Current != null)
{
    this.Dispatcher = Application.Current.Dispatcher;
}
else
{
    this.Dispatcher = Dispatcher.CurrentDispatcher;
}
Run Code Online (Sandbox Code Playgroud)

我是否需要做一些事情来初始化Dispatcher进行单元测试?Dispatcher永远不会在委托中运行代码.

jbe*_*jbe 87

通过使用Visual Studio单元测试框架,您无需自己初始化Dispatcher.您完全正确,Dispatcher不会自动处理其队列.

您可以编写一个简单的帮助方法"DispatcherUtil.DoEvents()",它告诉Dispatcher处理其队列.

C#代码:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

您也可以在WPF应用程序框架(WAF)中找到此类.

  • 我更喜欢这个答案,因为这个解决方案可以在顺序编写的测试用例中运行,而接受的答案要求测试代码是用面向回调的方法编写的. (4认同)
  • 您的DoEvents将冒执行由*其他单元测试*调度的调用,导致非常难以调试的失败:-(我发现这篇文章是因为有人在我们的单元测试中添加了DispatcherUtil示例代码的逐字副本并导致了非常的问题.我认为按照@OrionEdwards的建议将调度程序隐藏在接口后面是一种更好的方法,尽管我会在单元测试中使用带有实际队列的实现和显式出列的方法.如果我开始实现它我会在这里添加或编辑答案. (4认同)
  • 这对我来说非常出色.这是我接受的答案 (3认同)

Ori*_*rds 21

我们通过简单地模拟接口后面的调度程序,并从IOC容器中提取接口来解决这个问题.这是界面:

public interface IDispatcher
{
    void Dispatch( Delegate method, params object[] args );
}
Run Code Online (Sandbox Code Playgroud)

这是在真实应用程序的IOC容器中注册的具体实现

[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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我们在单元测试期间提供给代码的模拟代码:

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

我们还有一个MockDispatcher在后台线程中执行委托的变体,但在大多数情况下它并不是必需的


Ste*_*cht 17

您可以使用调度程序进行单元测试,只需使用DispatcherFrame即可.下面是我的一个单元测试的示例,它使用DispatcherFrame强制调度程序队列执行.

[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.");
}
Run Code Online (Sandbox Code Playgroud)

我在这里发现了它.


inf*_*ius 5

我通过在我的单元测试设置中创建一个新的应用程序解决了这个问题。

然后任何被测类访问 Application.Current.Dispatcher 都会找到一个调度程序。

因为 AppDomain 中只允许一个应用程序,所以我使用了 AssemblyInitialize 并将其放入自己的类 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
    }
}
Run Code Online (Sandbox Code Playgroud)