【问题标题】:How can I avoid cross-threading issues in my WPF MVVM unit tests?如何在我的 WPF MVVM 单元测试中避免跨线程问题?
【发布时间】:2013-03-06 14:24:21
【问题描述】:

我有一个使用 MVVM 模式的 WPF 项目。

在特定的视图模型中,我使用后台任务(Task 类)定期填充 ObservableCollection。

我使用下面的代码来实际填充集合:

    private void InitialiseAssignablePermissions()
    {
        var assignablePermissions = DetermineAssignablePermissions();

        CurrentDispatcher.Invoke(() => 
        {
            foreach (var ap in assignablePermissions)
            {
                AssignablePermissions.Add(ap);
            }
        });
    }

这很好用,我的单元测试运行良好,一切顺利。

但是,如果我有一个连接到 ObservableCollection 的 ICollectionView,当我运行测试时,我会收到一个跨线程异常并且测试失败。当我第一次尝试将项目添加到集合中时会发生异常。尽管在执行项目时,代码仍然可以愉快地运行。我需要集合视图,因为我想过滤项目。

例外是:

This type of CollectionView does not support changes to its
SourceCollection from a thread different from the Dispatcher thread.

CurrentDispatcher 类是我为单元测试添加的一个简单类:

internal static class CurrentDispatcher
{
    internal static void Invoke(Action action)
    {
        if (App.Current != null)
            App.Current.Dispatcher.Invoke(action);
        else
            action();
    }
}

如何添加集合视图并仍然进行单元测试?

【问题讨论】:

  • 您是否在单元测试期间检查过 App.Current 不等于 null?您还应该检查 Dispatcher.Thread.IsAlive 以防万一。

标签: wpf unit-testing


【解决方案1】:

我在为我的 WPF 项目编写测试时遇到了同样的问题。问题是在运行测试项目时调用 invoke 时调度程序没有运行。

您可以通过在单独的线程上运行调度程序来解决此问题。您可能会注意到,错误的堆栈跟踪会在失败时显示,因此我不会重新抛出从调度程序接收到的未处理异常,而是调用 assert.fail 并将堆栈跟踪附加到错误消息中。如果有人知道更好的处理方法,请告诉我。

这是我最终使用的代码:

public static void RunTestInDispatcher(Action action)
    {
        failException = null;
        GetDispatcher().Invoke(action);

        if (failException != null)
        {
            Assert.Fail(string.Format("{0}\n{1}", failException.Message, failException.StackTrace));
        }
    }

    private static object dispatcherLock = new object();
    private static Dispatcher dispatcher = null;
    public static Dispatcher GetDispatcher()
    {
        lock (dispatcherLock)
        {
            if (dispatcher == null)
            {
                Thread t = new Thread(new ThreadStart(() =>
                {
                    lock (dispatcherLock)
                    {
                        dispatcher = Dispatcher.CurrentDispatcher;
                        dispatcher.UnhandledException += new DispatcherUnhandledExceptionEventHandler(dispatcher_UnhandledException);
                        Monitor.Pulse(dispatcherLock);
                    }
                    Dispatcher.Run();
                }));

                t.SetApartmentState(ApartmentState.STA);
                t.Start();

                Monitor.Wait(dispatcherLock);
            }
        }
        return dispatcher;

    }

    static Exception failException = null;

    static void dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
    {
        e.Handled = true;
        failException = e.Exception;
    }

【讨论】:

  • 我假设测试然后被包裹在一个动作委托中?例如RunTestInDispatcher(() => { /* 动作和断言 */ }); - 这是我尝试过的,但我仍然收到错误。
  • 您应该只包装与 Dispatcher 相关的调用。断言应该在外面。
猜你喜欢
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 2020-01-18
  • 2012-09-02
  • 1970-01-01
  • 2021-02-24
  • 1970-01-01
  • 2016-11-22
相关资源
最近更新 更多