如何在WPF MVVM单元测试中避免交叉线程问题?

时间:2013-02-28 15:49:03

标签: wpf unit-testing

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

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

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

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

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

这非常有效,我的单元测试运行愉快,并且都变绿了。

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

例外是:

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

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

1 个答案:

答案 0 :(得分:0)

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