单元测试时挂起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.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中的依赖注入》中。