单元测试时挂起Application.Current.Dispatcher.Invoke(action)

本文关键字:Invoke action Dispatcher Current 挂起 Application 单元测试 | 更新日期: 2023-09-27 18:20:54

我正在为视图模型编写单元测试。该应用程序是使用Caliburn.Micro编写的,以支持MVVM。许多视图模型依赖Application.Current.Dispatcher,目的是将一些代码调度到UI线程中。

为了在测试中创建应用程序对象,我编写了以下类:

public class AppInitializer {
    private static Application app;
    public static void InitApp() {
        app = app ?? (app = Application.Current ?? new Application());
    }
}

现在我只在每个测试类中做以下操作:

[ClassInitialize]
    public static void InitClass(TestContext ctx) {
        AppInitializer.InitApp();
    }

不幸的是,从视图模型中对Application.Current.Dispatcher的第一次调用挂起了我的测试,直到超时为止。

我不想以某种方式抽象Application.CurrentDispatcher,我不想再向视图模型传递一个模拟对象。如果可能的话,我想找到一些变通办法。

单元测试时挂起Application.Current.Dispatcher.Invoke(action)

我认为您错过了对Application.Run的调用。您说得对,Application类的职责之一是在当前正在执行的线程上创建并启动Dispatcher,但所有这些都发生在对Run的调用过程中。

这就是问题的开始:Run是一个阻塞调用,即在Run退出之前,单元测试不会执行。在Store Apps中,有一个名为UITestMethod的特殊属性,但我认为它在WPF中不可用(尤其是如果您没有使用MSTest)。

那么你有什么选择呢?您可以在与运行单元测试的线程不同的线程上创建应用程序,但这将导致对Join的方法调用,因为您必须查看您的调用是否已调度到其他线程。这可能会导致单元测试缓慢。

您甚至不能在执行单元测试的线程上手动创建调度器,因为这与前面提到的App类相同:Dispatcher.Run是一个阻塞调用。

这就是为什么我建议您为Dispatcher创建一个抽象并注入它——这会为您节省很多痛苦。

环境上下文更新:

在评论中,我提到环境上下文是一种不依赖于将对象注入符合Dispatcher抽象的视图模型的解决方案。这就是它在代码中的样子:

public interface IDispatcher
{
    void ExecuteOnUIThread(Action action);
    // Add whatever methods you need on this interface
}
public static class DispatcherContext
{
    // An instance that implements IDispatcher can be accessed via this static property
    public static IDispatcher Dispatcher { get; set; }
}
// Of course you need to write an adapter for the WPF Dispatcher class

通过这种方式,您可以为单元测试创建一个dispatcher mock,但仍然可以通过视图模型中的静态属性访问它。您可以在上了解有关此模式的更多信息http://blogs.msdn.com/b/ploeh/archive/2007/07/23/ambientcontext.aspx或者在Mark Seemann的优秀著作《.NET中的依赖注入》中。